TMS Webhooks Developer Guide

Last updated: September 10, 2026

Webhooks let your TMS receive real-time notifications when something changes on a job in Denim, instead of polling the API. Denim sends an HTTP POST request with a JSON payload to an endpoint you register, as soon as the underlying event happens.

Webhooks are a complement to the Denim Public API — use the API to fetch full job state on demand, and webhooks to know when to fetch it (or to fetch a subset of what a webhook already gives you).

How delivery works

  1. Something happens on a job in Denim (for example, a job is approved).

  2. Denim publishes an internal event describing that change.

  3. Our webhook delivery service builds the event payload and sends it to your registered endpoint as an HTTP POST with a JSON body.

Webhooks are sent at least once. Use source_event_id (see below) to deduplicate on your end if you receive the same event more than once — for example, after a retried delivery.

We recommend treating webhooks as a signal to sync, not as the sole source of truth. If you ever suspect you’ve missed an event, the corresponding job can always be re-fetched from the API.

Retries

If a delivery attempt doesn’t get a successful (2xx) response from your endpoint — including a non-2xx status, a timeout, or a connection error — Denim retries it. We attempt delivery up to 5 times total, spaced out with increasing delay: immediately, then after 5 minutes, 30 minutes, 4 hours, and 20 hours — spreading the 5 attempts across roughly 24 hours. If the 5th attempt still fails, the event is abandoned and no further attempts are made.

Every retry of the same event carries the same source_event_id as the original attempt, so the dedup guidance above applies to retries too. If an event is ever abandoned before it reaches you, nothing about the underlying job/obligation state is lost — re-fetch it from the API using job_uuid whenever you suspect you’ve missed something.

Getting set up

Webhooks are not yet self-serve. When they are, you can log in to Denim, click on “Integrations”, and “Configure” in the Webhooks container to get started.

In the Configure Webhooks modal that pops up, you will need to enter:

  1. URL where you’d like Webhooks to be sent. This should be obtainable from your TMS Provider.

  2. Optionally, an API Key (also provided by your TMS Provider) that will identify you as the recipient of the webhook. a. We will send this value via X-API-KEY header in the request. We do not generate this value; it is something that, if present in the configuration, we will send with all webhook requests.

Verifying requests

We currently do not sign webhook payloads (for example, no HMAC signature header). To verify that a request genuinely came from Denim today, you have two options:

  • IP allowlisting — Webhook requests from Denim (Production) will come from 35.231.193.139.

  • A shared API key — the API Key entered during configuration can also serve as a point of verification.

The event envelope

Every webhook, regardless of type, is sent in the same outer shape:

Field

Type

Description

source_event_id

string (UUID)

Unique ID of the underlying event that produced this webhook. Use this to deduplicate retried/duplicate deliveries.

webhook_type

string

Dot-namespaced event name, e.g. job.approved. Use this to decide how to parse data.

occurred_at

string (ISO 8601 datetime)

When the underlying change happened in Denim.

job_uuid

string (UUID)

The job this event relates to.

data

object

The event-specific payload. Shape depends on webhook_type — see below.

All monetary amounts throughout every payload are integers in cents — e.g. $2,850.00 will come across as 285000.

One structural note that applies to every webhook type below except job.approveddata.job is a minimal reference —

{ "uuid": "...", "reference_number": "...", "status": "..." }

— rather than the full job snapshot. If you need full job detail for one of these events, look it up via the API using job_uuid.


job.approved

Sent when a job is approved. data.job on this event will contain a full snapshot of the job at the moment it was approved:

Field

Type

Description

uuid

string (UUID)

Job identifier.

is_factored

boolean

Whether the job is factored.

status

string

Job status at the time of approval ("approved").

status_message

string | null

Optional status detail.

reference_number

string | null

Your reference number for the job.

company_name

string | null

Name of the job’s company.

created_at

string (ISO 8601)

When the job was created.

created_by_user_full_name

string | null

Name of the user who created the job, if available.

actionable_doc_audit_issue_count

integer

Count of open document-audit issues requiring action.

needs_attention

boolean

Whether the job is flagged as needing attention.

obligations

array

See obligation shape below.

documents

array

See document shape below.

Each entry in obligations (this same shape is reused inside job.payment and job.audit_issue.* below):

Field

Type

Description

uuid

string (UUID)

Obligation identifier.

client_debtor_relationship_id

integer | null

Set for receivable obligations.

client_payee_relationship_id

integer | null

Set for payable obligations to a contractor/carrier.

total_amount

integer

Total amount, in cents.

applied_amount

integer | null

Amount applied so far, in cents.

amount_due

integer | null

Remaining amount due, in cents.

due_date

string (ISO 8601)

When the obligation is due.

obligation_date

string (ISO 8601)

The obligation’s effective date.

type

string

"receivable" or "payable".

subtype

string

e.g. "primary""advance""rebate".

payment_status

string

Customer-facing payment status (e.g. "expected""fully_paid").

reference_number

string | null

Reference number for this obligation.

company_name

string | null

Name of the counterparty company.

factor_company_name

string | null

Name of the factor, if the obligation is paid to/by a factor.

is_express_factored

boolean | null

Whether the obligation was express-factored.

line_items

array

Breakdown of the obligation — see below.

Each entry in obligations[].line_items:

Field

Type

Description

id

integer

Line item identifier.

type

string

One of the line item types in the table below.

description

string | null

Human-readable description.

amount

integer

Amount, in cents. Negative for fees; positive for the base amount and most accessorials — see below.

rate

number | null

Basis-points rate, if the line item is rate-based (currently only quickpay and servicing_fee). null otherwise.

obligation_uuid

string (UUID)

The obligation this line item belongs to.

An obligation’s line_items[].amount values always sum to its total_amount. An obligation with no fees or accessorials applied has no need to itemize and comes across with line_items: [].

Line item types

type

Where it comes from

amount

rate

description

base_amount

System-generated. The obligation’s gross amount before any fees or adjustments. Only present once the obligation has at least one other line item — an obligation with no fees/accessorials has line_items: [] instead.

Positive.

null

Always "Base Amount".

accessorial_fee

Submitted by the client/TMS via the API (e.g. detention, lumper, layover), or added automatically when a document-audit resolution pulls a matching line item off the carrier invoice/rate confirmation.

Positive.

null

Free text supplied by whoever added the line item (max 40 chars), e.g. "Detention".

adjustment

A manual correction to the obligation total, submitted via the API/UI.

Either sign.

null

Free text.

quickpay

QuickPay. System-generated discount fee charged to a payee being paid directly (no factor) in exchange for faster payment. Only appears on non-client payable obligations where a QuickPay rate applies.

Negative — basis points of the adjusted base amount.

The basis-points rate used, e.g. 150 = 1.50%.

Always "QuickPay Fee".

express_factoring_fee

Express Factoring / Express Pay. System-generated fee charged when an obligation is expedited via RTP. Appears once the obligation is (or becomes) is_express_factored: true and the fee is passed on to that party.

Negative — a flat fee from the client’s policy.

null (flat fee, not rate-based).

Always "Express Factoring Fee".

servicing_fee

System-generated servicing fee taken from the job total per the client’s policy. Lives on the job’s earnings-type servicing-fee obligation, not on payable/receivable obligations.

Negative.

The basis-points rate from the client’s policy.

Always "Servicing Rate Fee".

job_processing_fee

System-generated flat per-job fee from the client’s policy.

Negative.

null

Always "Job Processing Fee".

non_factoring_fee

System-generated flat fee charged on non-factored jobs with a receivable.

Negative.

null

Always "Non Factoring Fee".

instantpayprimary_receivable_fee

Legacy values kept for schema compatibility. No current code path produces either — you shouldn’t see them on new obligations.

QuickPay and Express Factoring fees ride along in line_items like any other line item — there’s no separate top-level field for them (is_express_factored on the obligation is the one exception: it’s a boolean flag alongside line_items, not a substitute for the express_factoring_fee line item itself).

Each entry in documents (this same shape is reused inside job.audit_issue.* below):

Field

Type

Description

uuid

string (UUID)

Document identifier.

file_name

string

Original file name.

type

string

e.g. "pod""customer_rate_con""carrier_invoice".

subtype

string | null

Optional document subtype.

inserted_at

string (ISO 8601)

When the document was uploaded.

url

string

A temporary download link. Expires 24 hours after the webhook is sent — if you need the file later, re-fetch it from the API.

Example: job.approved

{
  "source_event_id": "018f2b6a-9c3d-7a1e-b5a2-4d3f1e9a2b70",
  "webhook_type": "job.approved",
  "occurred_at": "2026-07-31T14:23:05Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "is_factored": true,
      "status": "approved",
      "status_message": null,
      "reference_number": "TS-482913",
      "company_name": "Summit Freight Brokers LLC",
      "created_at": "2026-07-28T18:02:11Z",
      "created_by_user_full_name": "Jamie Rivera",
      "actionable_doc_audit_issue_count": 0,
      "needs_attention": false,
      "obligations": [
        {
          "uuid": "b3f2e4a1-8d3c-4a2e-9f11-7c6d5e4a3b21",
          "client_debtor_relationship_id": 340,
          "client_payee_relationship_id": null,
          "total_amount": 285000,
          "applied_amount": 0,
          "amount_due": 285000,
          "due_date": "2026-08-30T00:00:00Z",
          "obligation_date": "2026-07-31T00:00:00Z",
          "type": "receivable",
          "subtype": "primary",
          "payment_status": "expected",
          "reference_number": "INV-98213",
          "company_name": "Northbound Debtor Co.",
          "factor_company_name": null,
          "is_express_factored": true,
          "line_items": [
            { "id": 501, "type": "base_amount", "description": "Base Amount", "amount": 260000, "rate": null, "obligation_uuid": "b3f2e4a1-8d3c-4a2e-9f11-7c6d5e4a3b21" },
            { "id": 502, "type": "accessorial_fee", "description": "Detention", "amount": 25000, "rate": null, "obligation_uuid": "b3f2e4a1-8d3c-4a2e-9f11-7c6d5e4a3b21" }
          ]
        },
        {
          "uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f",
          "client_debtor_relationship_id": null,
          "client_payee_relationship_id": 512,
          "total_amount": 235000,
          "applied_amount": 0,
          "amount_due": 235000,
          "due_date": "2026-08-15T00:00:00Z",
          "obligation_date": "2026-07-31T00:00:00Z",
          "type": "payable",
          "subtype": "primary",
          "payment_status": "expected",
          "reference_number": "PO-33210",
          "company_name": "Rolling Thunder Trucking",
          "factor_company_name": null,
          "is_express_factored": false,
          "line_items": [
            { "id": 503, "type": "base_amount", "description": "Base Amount", "amount": 235000, "rate": null, "obligation_uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f" }
          ]
        }
      ],
      "documents": [
        { "uuid": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "file_name": "rate_con_TS-482913.pdf", "type": "customer_rate_con", "subtype": null, "inserted_at": "2026-07-30T12:00:00Z", "url": "https://link-to-gcp-file.gcp.com" },
        { "uuid": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f", "file_name": "pod_TS-482913.pdf", "type": "pod", "subtype": null, "inserted_at": "2026-07-30T16:20:00Z", "url": "https://link-to-gcp-file.gcp.com" }
      ]
    }
  }
}

job.rejected

Sent when a job fails manual review and is rejected. data.job is the minimal reference; data.manual_review.message carries a human-readable explanation.

{
  "source_event_id": "4f5a6b7c-8d9e-4f0a-9b1c-2d3e4f5a6b70",
  "webhook_type": "job.rejected",
  "occurred_at": "2026-07-31T09:47:22Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "rejected"
    },
    "manual_review": {
      "message": "Job Rejected: Proof of delivery not received; Rate confirmation amount does not match invoice"
    }
  }
}

job.resolved

The inverse of job.rejected — sent when a previously rejected/flagged job is corrected and returned to normal processing. Same shape, different message content and job status.

{
  "source_event_id": "5a6b7c8d-9e0f-4a1b-9c2d-3e4f5a6b7c80",
  "webhook_type": "job.resolved",
  "occurred_at": "2026-07-31T10:15:49Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "pending"
    },
    "manual_review": {
      "message": "Job Resolved: Proof of delivery and corrected rate confirmation uploaded; job returned for reprocessing."
    }
  }
}

job.manual_review.failed / job.manual_review.resolved

Sent when a job is flagged for manual review at any of three separate processing stages (data processing, review, and third-party/debtor-portal verification), and again when that flag clears. Regardless of which stage triggered it, the webhook shape is identical — only data.manual_review.message differs, describing what was flagged or resolved at that stage.

{
  "source_event_id": "7e8f9a0b-1c2d-4e3f-9a4b-5c6d7e8f9a10",
  "webhook_type": "job.manual_review.failed",
  "occurred_at": "2026-07-31T11:03:18Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "pending"
    },
    "manual_review": {
      "message": "Issue found in review"
    }
  }
}
{
  "source_event_id": "8f9a0b1c-2d3e-4f4a-9b5c-6d7e8f9a0b20",
  "webhook_type": "job.manual_review.resolved",
  "occurred_at": "2026-07-31T15:47:02Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "pending"
    },
    "manual_review": {
      "message": "Review Passed"
    }
  }
}

job.payment

The most involved webhook type — worth reading carefully. It’s sent whenever a payment is applied to, or removed from, an obligation on a job, which covers ordinary payments, chargebacks, and givebacks alike.

Overall shape: data.job is the minimal reference, and data.payment_data is always an array of payment entries — one consistent parsing contract regardless of which internal event produced the webhook. Most of the time it has exactly one entry; it can have more when a single action affects several obligations at once (for example, reversing a payment that had touched a receivable, a contractor payable, and a client advance together).

Each entry in payment_data embeds a full obligation snapshot (same shape as in job.approved, above) plus these fields:

Field

Type

Description

obligation

object

Full obligation snapshot — same shape as job.approved’s obligations[].

obligation_type

string

"receivable""client_advance""contractor_payable", or "rebate" — derived, not a raw field (see below).

payment_type

string

What kind of payment action this is — see below.

applied_amount

integer

Amount involved, in cents. Sign and meaning depend on payment_type (see below).

obligation_type is derived from the obligation’s type/subtype and whether its company matches the job’s company:

obligation_type

Derived from

receivable

obligation type == "receivable" and subtype == "primary"

client_advance

obligation type == "payable"subtype == "advance", and same company as the job

contractor_payable

obligation type == "payable"subtype in ["primary", "advance"], and a different company than the job

rebate

obligation type == "payable"subtype == "rebate", and same company as the job

payment_type tells you what happened, and which additional fields to expect alongside the ones above:

payment_type

Meaning

Additional fields

payment_applied

An ordinary payment was applied to the obligation.

applied_transaction_uuidtransaction_uuidtransaction_datetransaction_type ("incoming"/"outgoing"), obligation_company_idtransaction_company_id

payment_unapplied

A previously applied payment was reversed.

Same additional fields as payment_applied.

chargeback_created

A chargeback was recorded against the obligation.

obligation_entry_uuidtarget_company_iddescriptionapplied_at

giveback_created

A giveback was recorded against the obligation.

Same additional fields as chargeback_created.

chargeback_reverted

A previously created chargeback was reversed.

obligation_entry_uuid only — no descriptiontarget_company_id, or applied_at (the reversal event doesn’t carry them).

giveback_reverted

A previously created giveback was reversed.

Same as chargeback_reverted.

One subtlety on applied_amount: for chargeback_created/giveback_created, it’s the sign-adjusted value — for example, a chargeback against a client_advance obligation carries applied_amount: -225000. For *_reverted types, applied_amount is already the fully reversal-signed value (the negation of what was originally applied).

Example: payment_unapplied touching three obligations in one reversal

Shows the array shape and all three of receivable/contractor_payable/client_advance in one payload (rebate follows the same pattern, just with subtype == "rebate"):

{
  "source_event_id": "2c3d4e5f-6a7b-4c8d-9e9f-0a1b2c3d4e50",
  "webhook_type": "job.payment",
  "occurred_at": "2026-08-05T10:22:47Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "approved"
    },
    "payment_data": [
      {
        "obligation": {
          "uuid": "b3f2e4a1-8d3c-4a2e-9f11-7c6d5e4a3b21",
          "client_debtor_relationship_id": 340,
          "client_payee_relationship_id": null,
          "total_amount": 285000,
          "applied_amount": 285000,
          "amount_due": 0,
          "due_date": "2026-08-30T00:00:00Z",
          "obligation_date": "2026-07-31T00:00:00Z",
          "type": "receivable",
          "subtype": "primary",
          "payment_status": "fully_paid",
          "reference_number": "INV-98213",
          "company_name": "Northbound Debtor Co.",
          "factor_company_name": null,
          "is_express_factored": true,
          "line_items": [
            { "id": 501, "type": "base_amount", "description": "Base Amount", "amount": 260000, "rate": null, "obligation_uuid": "b3f2e4a1-8d3c-4a2e-9f11-7c6d5e4a3b21" },
            { "id": 502, "type": "accessorial_fee", "description": "Detention", "amount": 25000, "rate": null, "obligation_uuid": "b3f2e4a1-8d3c-4a2e-9f11-7c6d5e4a3b21" }
          ]
        },
        "obligation_type": "receivable",
        "payment_type": "payment_unapplied",
        "applied_amount": 285000,
        "applied_transaction_uuid": "4a5b6c7d-8e9f-4a0b-9c1d-2e3f4a5b6c70",
        "transaction_uuid": "5b6c7d8e-9f0a-4b1c-9d2e-3f4a5b6c7d80",
        "transaction_date": "2026-08-05T10:22:40Z",
        "transaction_type": "incoming",
        "obligation_company_id": 5510,
        "transaction_company_id": 5510
      },
      {
        "obligation": {
          "uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f",
          "client_debtor_relationship_id": null,
          "client_payee_relationship_id": 512,
          "total_amount": 235000,
          "applied_amount": 235000,
          "amount_due": 0,
          "due_date": "2026-08-15T00:00:00Z",
          "obligation_date": "2026-07-31T00:00:00Z",
          "type": "payable",
          "subtype": "primary",
          "payment_status": "fully_paid",
          "reference_number": "PO-33210",
          "company_name": "Rolling Thunder Trucking",
          "factor_company_name": null,
          "is_express_factored": false,
          "line_items": [
            { "id": 503, "type": "base_amount", "description": "Base Amount", "amount": 235000, "rate": null, "obligation_uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f" }
          ]
        },
        "obligation_type": "contractor_payable",
        "payment_type": "payment_unapplied",
        "applied_amount": 235000,
        "applied_transaction_uuid": "0a1b2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c30",
        "transaction_uuid": "1b2c3d4e-5f6a-4b7c-9d8e-9f0a1b2c3d40",
        "transaction_date": "2026-08-05T10:22:42Z",
        "transaction_type": "outgoing",
        "obligation_company_id": 6002,
        "transaction_company_id": 6002
      },
      {
        "obligation": {
          "uuid": "7a8b9c0d-1e2f-4a3b-9c4d-5e6f7a8b9c0d",
          "client_debtor_relationship_id": null,
          "client_payee_relationship_id": null,
          "total_amount": 225000,
          "applied_amount": 225000,
          "amount_due": 0,
          "due_date": "2026-07-31T00:00:00Z",
          "obligation_date": "2026-07-31T00:00:00Z",
          "type": "payable",
          "subtype": "advance",
          "payment_status": "fully_paid",
          "reference_number": null,
          "company_name": "Summit Freight Brokers LLC",
          "factor_company_name": null,
          "is_express_factored": true,
          "line_items": []
        },
        "obligation_type": "client_advance",
        "payment_type": "payment_unapplied",
        "applied_amount": 225000,
        "applied_transaction_uuid": "7d8e9f0a-1b2c-4d3e-9f4a-5b6c7d8e9f00",
        "transaction_uuid": "8e9f0a1b-2c3d-4e4f-9a5b-6c7d8e9f0a10",
        "transaction_date": "2026-08-05T10:22:45Z",
        "transaction_type": "outgoing",
        "obligation_company_id": 4821,
        "transaction_company_id": 4821
      }
    ]
  }
}

Example: chargeback_created

Shows the entry-specific fields (obligation_entry_uuidtarget_company_iddescriptionapplied_at) that appear on the chargeback/giveback family instead of the transaction fields above. A giveback_created payload looks identical aside from payment_type and obligation_type*_reverted variants drop descriptiontarget_company_id, and applied_at, keeping only obligation_entry_uuid.

{
  "source_event_id": "5a6b7c8d-9e0f-4a1b-9c2d-3e4f5a6b7c90",
  "webhook_type": "job.payment",
  "occurred_at": "2026-08-20T13:05:22Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "approved"
    },
    "payment_data": [
      {
        "obligation": {
          "uuid": "b3f2e4a1-8d3c-4a2e-9f11-7c6d5e4a3b21",
          "client_debtor_relationship_id": 340,
          "client_payee_relationship_id": null,
          "total_amount": 285000,
          "applied_amount": 285000,
          "amount_due": 0,
          "due_date": "2026-08-30T00:00:00Z",
          "obligation_date": "2026-07-31T00:00:00Z",
          "type": "receivable",
          "subtype": "primary",
          "payment_status": "fully_paid",
          "reference_number": "INV-98213",
          "company_name": "Northbound Debtor Co.",
          "factor_company_name": null,
          "is_express_factored": true,
          "line_items": [
            { "id": 501, "type": "base_amount", "description": "Base Amount", "amount": 260000, "rate": null, "obligation_uuid": "b3f2e4a1-8d3c-4a2e-9f11-7c6d5e4a3b21" },
            { "id": 502, "type": "accessorial_fee", "description": "Detention", "amount": 25000, "rate": null, "obligation_uuid": "b3f2e4a1-8d3c-4a2e-9f11-7c6d5e4a3b21" }
          ]
        },
        "obligation_type": "receivable",
        "payment_type": "chargeback_created",
        "applied_amount": 285000,
        "obligation_entry_uuid": "6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d90",
        "target_company_id": 4821,
        "description": "Full chargeback — debtor disputed invoice, payment not received",
        "applied_at": "2026-08-20T13:05:20Z"
      }
    ]
  }
}

job.audit_issue.failed / job.audit_issue.resolved

Sent for each of nine document-audit issue types we check for — job.audit_issue.failed when DocAudit flags one, and a matching job.audit_issue.resolved when that same issue is later resolved.

Overall shape: data.job is the minimal reference; data.issue carries the issue itself:

Field

Type

Description

issue_uuid

string (UUID)

Identifies this specific issue — the same value on both the failed and later resolved event, so you can match them up.

issue_type

string

One of the nine types below.

status

string

"action_required" on .failed"resolved" on .resolved.

obligation

object

Full obligation snapshot — same shape as job.approved’s obligations[].

document

object | null

Same shape as job.approved’s documents[]null when the issue is that a document is missing (see “Missing document” category below).

audit_result_uuid

string (UUID)

Identifies the audit run that produced this issue.

details

object

Issue-specific — shape depends on issue_type and status, see below.

The nine issue types fall into three categories, each with its own details shape:

Mismatch (amount_mismatchcompany_name_mismatchreference_number_mismatch) — the document exists, but something on it doesn’t match what’s expected. On .faileddetails has checkdocument_uuid, an expected_*/found_* pair specific to what’s being checked, and page_numbers. On .resolved, it has checkdocument_uuid, a resolved_* value, and resolution_reason.

Missing document (missing_pod_documentmissing_customer_rate_con_documentmissing_carrier_invoice_document) — the document doesn’t exist yet. document is null on .failed. On .faileddetails is just expected_document_type; on .resolved, once it’s uploaded, details adds document_uuid and resolution_reason, and document is populated.

Miscategorized document (miscategorized_pod_documentmiscategorized_customer_rate_documentmiscategorized_carrier_invoice_document) — the document exists but was classified as the wrong type. On .faileddetails has document_uuidexpected_document_category ("billing_paperwork" for POD/rate-con, "supporting_paperwork" for carrier invoice), expected_document_type, and found_classification. On .resolvedfound_classification is replaced with resolution_reason.

Example: amount_mismatch (mismatch category)

{
  "source_event_id": "5e7a2c14-3f8b-4d1a-9e6c-8b2f5a4d3c17",
  "webhook_type": "job.audit_issue.failed",
  "occurred_at": "2026-07-31T09:12:03Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "approved"
    },
    "issue": {
      "issue_uuid": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "issue_type": "amount_mismatch",
      "status": "action_required",
      "obligation": {
        "uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f",
        "client_debtor_relationship_id": null,
        "client_payee_relationship_id": 512,
        "total_amount": 235000,
        "applied_amount": 0,
        "amount_due": 235000,
        "due_date": "2026-08-15T00:00:00Z",
        "obligation_date": "2026-07-31T00:00:00Z",
        "type": "payable",
        "subtype": "primary",
        "payment_status": "expected",
        "reference_number": "PO-33210",
        "company_name": "Rolling Thunder Trucking",
        "factor_company_name": null,
        "is_express_factored": false,
        "line_items": [
          { "id": 503, "type": "base_amount", "description": "Base Amount", "amount": 235000, "rate": null, "obligation_uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f" }
        ]
      },
      "document": {
        "uuid": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
        "file_name": "rate_con_TS-482913.pdf",
        "type": "customer_rate_con",
        "subtype": null,
        "inserted_at": "2026-07-30T12:00:00Z",
        "url": "https://link-to-gcp-file.gcp.com"
      },
      "audit_result_uuid": "d4e5f6a7-8b9c-4d1e-9f2a-3b4c5d6e7f80",
      "details": {
        "check": "total_amount_check",
        "document_uuid": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
        "expected_amount": 235000,
        "found_amount": 228500,
        "page_numbers": [2]
      }
    }
  }
}
{
  "source_event_id": "6f8b3d25-4a9c-4e2b-9f7d-9c3a6b5e4d28",
  "webhook_type": "job.audit_issue.resolved",
  "occurred_at": "2026-08-01T10:45:12Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "approved"
    },
    "issue": {
      "issue_uuid": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "issue_type": "amount_mismatch",
      "status": "resolved",
      "obligation": {
        "uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f",
        "client_debtor_relationship_id": null,
        "client_payee_relationship_id": 512,
        "total_amount": 235000,
        "applied_amount": 0,
        "amount_due": 235000,
        "due_date": "2026-08-15T00:00:00Z",
        "obligation_date": "2026-07-31T00:00:00Z",
        "type": "payable",
        "subtype": "primary",
        "payment_status": "expected",
        "reference_number": "PO-33210",
        "company_name": "Rolling Thunder Trucking",
        "factor_company_name": null,
        "is_express_factored": false,
        "line_items": [
          { "id": 503, "type": "base_amount", "description": "Base Amount", "amount": 235000, "rate": null, "obligation_uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f" }
        ]
      },
      "document": {
        "uuid": "4d5e6f7a-8b9c-4d0e-9f1a-2b3c4d5e6f7a",
        "file_name": "rate_con_TS-482913.pdf",
        "type": "customer_rate_con",
        "subtype": null,
        "inserted_at": "2026-07-30T12:00:00Z",
        "url": "https://link-to-gcp-file.gcp.com"
      },
      "audit_result_uuid": "e5f6a7b8-9c0d-4e2f-9a3b-4c5d6e7f8a91",
      "details": {
        "check": "total_amount_check",
        "document_uuid": "4d5e6f7a-8b9c-4d0e-9f1a-2b3c4d5e6f7a",
        "resolved_amount": 235000,
        "resolution_reason": "document_reaudited"
      }
    }
  }
}

Example: missing_pod_document (missing document category)

Note "document": null on the .failed event — the document doesn’t exist yet, so there’s nothing to reference until it’s uploaded and the issue resolves.

{
  "source_event_id": "9f3d1a2b-6c4e-4f8a-9b2d-1e3c5a7f9d40",
  "webhook_type": "job.audit_issue.failed",
  "occurred_at": "2026-07-31T09:12:03Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "approved"
    },
    "issue": {
      "issue_uuid": "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
      "issue_type": "missing_pod_document",
      "status": "action_required",
      "obligation": {
        "uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f",
        "client_debtor_relationship_id": null,
        "client_payee_relationship_id": 512,
        "total_amount": 235000,
        "applied_amount": 0,
        "amount_due": 235000,
        "due_date": "2026-08-15T00:00:00Z",
        "obligation_date": "2026-07-31T00:00:00Z",
        "type": "payable",
        "subtype": "primary",
        "payment_status": "expected",
        "reference_number": "PO-33210",
        "company_name": "Rolling Thunder Trucking",
        "factor_company_name": null,
        "is_express_factored": false,
        "line_items": [
          { "id": 503, "type": "base_amount", "description": "Base Amount", "amount": 235000, "rate": null, "obligation_uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f" }
        ]
      },
      "document": null,
      "audit_result_uuid": "d4e5f6a7-8b9c-4d1e-9f2a-3b4c5d6e7f80",
      "details": {
        "expected_document_type": "pod"
      }
    }
  }
}
{
  "source_event_id": "0a1b2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c30",
  "webhook_type": "job.audit_issue.resolved",
  "occurred_at": "2026-08-01T11:02:47Z",
  "job_uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
  "data": {
    "job": {
      "uuid": "0b1e9a3d-6f2c-4e11-9a77-2c8f4d1b3a90",
      "reference_number": "TS-482913",
      "status": "approved"
    },
    "issue": {
      "issue_uuid": "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
      "issue_type": "missing_pod_document",
      "status": "resolved",
      "obligation": {
        "uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f",
        "client_debtor_relationship_id": null,
        "client_payee_relationship_id": 512,
        "total_amount": 235000,
        "applied_amount": 0,
        "amount_due": 235000,
        "due_date": "2026-08-15T00:00:00Z",
        "obligation_date": "2026-07-31T00:00:00Z",
        "type": "payable",
        "subtype": "primary",
        "payment_status": "expected",
        "reference_number": "PO-33210",
        "company_name": "Rolling Thunder Trucking",
        "factor_company_name": null,
        "is_express_factored": false,
        "line_items": [
          { "id": 503, "type": "base_amount", "description": "Base Amount", "amount": 235000, "rate": null, "obligation_uuid": "4c5d6e7f-8a9b-4c1d-9e2f-3a4b5c6d7e8f" }
        ]
      },
      "document": {
        "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e60",
        "file_name": "pod_TS-482913.pdf",
        "type": "pod",
        "subtype": null,
        "inserted_at": "2026-07-30T12:00:00Z",
        "url": "https://link-to-gcp-file.gcp.com"
      },
      "audit_result_uuid": "1b2c3d4e-5f6a-4b7c-9d8e-9f0a1b2c3d40",
      "details": {
        "expected_document_type": "pod",
        "document_uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e60",
        "resolution_reason": "document_uploaded"
      }
    }
  }
}