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).
What you can subscribe to
The webhook event catalog is the single source of truth for every trigger string, across all resources — invoices, carrier invoices, documents, payments, exceptions, remittances, and approval requests. Two rules to internalize before subscribing:- Each change fires exactly one trigger — the most specific that applies. A tracked status
transition fires only its
.status.{STATUS}trigger, not also the genericupdate.{resource}; the generic trigger fires for changes that are neither a tracked status nor a named sub-event. Subscribe to every trigger you care about. - A few triggers exist in the dispatcher but are not self-service in the dashboard subscription form (they’re marked in the catalog) — contact support to enable those.
For carrier invoices specifically: “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:
Plus delivery bookkeeping:
tenantId, webhookSubscriptionId, createdAt, updatedAt,
scheduledAt, processedAt, retryOfId (set when this delivery is a retry of an earlier
event — dedupe on it), and retries (retry events spawned from this one).
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).
There is no payload signing (HMAC) or replay protection — authentication is the static
header or Basic credentials you configure above. Treat the shared secret like a password:
rotate it if it leaks, and always compare it on every delivery.
Reliability
- What retries: deliveries that return
5xx, time out, or fail at the network level are retried, up to the subscription’sretryAttempts. - What does NOT retry: a
4xxresponse is treated as delivered — it is recorded as successful and never retried. A misconfigured receiver (broken auth check, wrong route) that 4xxes will silently lose every event while Upwell’s side shows success. Alert on your receiver’s 4xx rate, and reconcile with polling (below). - Retry schedule: exponential backoff starting at 1 minute and doubling each attempt (1, 2, 4, 8, … minutes).
retryAttemptscounts retries beyond the first attempt —retryAttempts: 3(the default) means up to 4 total deliveries. The dashboard form accepts 0–10.- Each delivery attempt has a 15-second timeout. Return a
2xxquickly; do slow work asynchronously. - Build an idempotent receiver: dedupe on the delivery
id, and treat a non-nullretryOfIdas a redelivery of that earlier event. - After retries exhaust there is no automatic replay — pair webhooks with a periodic reconciliation poll so a missed delivery never strands a record (see Knowing when a carrier invoice is processed).
Example receiver
See it end-to-end
Pair the receiver above with a polling fallback, and walk the full submit → attach → process flow in the submission guide.

