Outbound webhooks
Upwell can POST a JSON event to a URL you host whenever something changes — a carrier invoice is created, matched to a shipment, approved, and so on. Webhooks are the push counterpart to polling.Subscribing
Webhook subscriptions are configured in the Upwell dashboard (there’s no public REST endpoint to create one). A subscription has:url— your HTTPS receiver.triggers— the list of event types you want delivered.authSettings— how Upwell proves the delivery came from Upwell (see Authenticating deliveries).retryAttempts— how many times Upwell retries a failed delivery (0–10).
Carrier-invoice triggers
| Trigger | Fires when |
|---|---|
create.carrier_invoice | A carrier invoice is created. |
update.carrier_invoice | Any field/status change on a carrier invoice. |
update.carrier_invoice.shipment_updated / create.carrier_invoice.shipment_updated | The invoice was matched/linked to a shipment (fires during ingestion). |
update.carrier_invoice.status.APPROVED | The invoice reached APPROVED (review lifecycle). |
update.carrier_invoice.status.EXCEPTION | The invoice hit an exception status (review lifecycle). |
create.carrier_document / update.carrier_document / delete.carrier_document | A document was added, (re)classified, or removed. |
“Ingestion done” (matched + audited) is best observed via
*.shipment_updated or the generic update.carrier_invoice. The *.status.APPROVED / *.status.EXCEPTION triggers reflect the later review lifecycle. See Knowing when a carrier invoice is processed.The delivery envelope
Every delivery is a single JSON object — the event row. The fields you’ll read:| Field | Description |
|---|---|
trigger | The trigger string, e.g. update.carrier_invoice.status.APPROVED. Branch on this. |
table | The resource table, e.g. carrier_invoices. |
operation | INSERT, UPDATE, or DELETE. |
resourceId | The id of the affected carrier invoice. Use it to correlate (and to fetch a canonical representation via GET /carrier_invoices/{resourceId}). |
payload | The event body — its shape depends on the trigger (see below). |
status | The delivery status of this webhook event (e.g. SUCCESS), not the invoice status. Don’t confuse the two. |
id | The delivery’s own id. Use it to dedupe retries. |
tenantId, retries, createdAt, processedAt, …).
The payload shape varies by trigger
Status-change events (update.carrier_invoice.status.APPROVED / .EXCEPTION) — payload.carrierInvoice with the invoice and its matched relations nested:
update.carrier_invoice.shipment_updated) — raw new/old snapshots (snake_case columns):
Note
new.source_system = "API" and new.integration_id are stamped by Upwell — confirming the invoice was API-submitted. source_system_id is null for API submissions.Authenticating deliveries
You choose how Upwell authenticates to your receiver when you create the subscription. Three options:Custom header (recommended)
Custom header (recommended)
You pick a header name and a secret value; Upwell sends that header on every delivery. A common choice is a token header:Your receiver compares the header against the secret you configured and rejects mismatches. The header name is your choice —
X-Upwell-Token is a convention, not a built-in.Basic auth
Basic auth
Upwell sends
Authorization: Basic base64(username:password) using the credentials you configure.None
None
No auth header. Rely on URL secrecy / network controls. Not recommended for production.
Content-Type: application/json and a User-Agent of UpwellWebhookService/<version> (https://www.upwell.com).
Reliability
- Deliveries that return
5xxare retried, up to the subscription’sretryAttempts. - Each delivery has a 15-second timeout.
- Return a
2xxquickly; do slow work asynchronously. - Build an idempotent receiver: dedupe on the delivery
id(orresourceId+trigger).
Example receiver
See it end-to-end
The sample TMS app implements this receiver alongside a polling fallback, and walks the full submit → attach → process flow. Start from the submission guide.

