Skip to main content

Knowing when a carrier invoice is processed

After you submit a carrier invoice, Upwell works on it asynchronously. This page explains how to learn when that work is done.
There’s no single “processed” flag. You read the existing fields on the invoice — its status, its exceptions, and the matched shipmentId — plus the type on each attached document. You can read them by polling the GET endpoints or by receiving webhooks. Most production integrations use webhooks for timeliness and polling as a reconciliation backstop.

Two phases of processing

It helps to separate two things:
  1. Ingestion (automatic, seconds after create). Upwell stamps provenance (source_system = "API"), matches the invoice to a shipment/carrier/bill from your references, and runs the exception audit. The status usually stays RECEIVED through this phase — ingestion populates shipmentId/billId/carrierId and updates exceptions, it doesn’t move the status.
  2. Review & approval (later; may involve your rules or a human). The invoice moves toward APPROVED or an exception state. This is where status changes.
So “is it done?” has two answers depending on what you’re waiting for. Both are observable from the same endpoints.

The signals

Status values

exceptions is a comma-joined string, not an array. Treat a non-empty string as “needs attention” even while status is still RECEIVED. And don’t judge exceptions until ingestion has run — a brand-new row always lists “no carrier / no matching shipment” findings that disappear once matching completes.

Approach 1 — Polling

Read the invoice and its documents on an interval. Wait for ingestion (the row’s updatedAt advances past createdAt), then read the outcome.
A minimal poller with backoff:
Start around a 1–2 s interval and back off exponentially. Ingestion usually settles within seconds; document classification can take longer for large multi-page PDFs.
The documents endpoint exposes the document’s type, not its internal classification status. A document still UNKNOWN past your timeout is ambiguous between “still classifying” and “couldn’t be classified” — treat it as needs manual review rather than a hard failure, and don’t block your pipeline on it.

Approach 2 — Webhooks

Subscribe once and let Upwell push the changes to you. Configure subscriptions in the Upwell dashboard (see Outbound webhooks for the model, payload shape, and auth).
The only per-status webhooks are APPROVED and EXCEPTION — and those reflect the review lifecycle, not ingestion. To know that ingestion completed (matched + audited), listen to update.carrier_invoice / update.carrier_invoice.shipment_updated, or poll. Other status transitions (UNDER_REVIEW, PAID, …) don’t each get their own trigger.

Which should I use?

Webhooks

Best when you can host an HTTPS endpoint. Near-real-time, no polling load.

Polling

Best when you can’t receive inbound requests, or as a backstop. Read GET /carrier_invoices/{id} (+ /documents) with backoff.
Webhook delivery retries on 5xx and can be delayed under load. A robust integration uses webhooks for timeliness and reconciles with a periodic poll so a missed delivery never leaves an invoice stuck in your system. The demo TMS app implements both.