Skip to main content

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 generic update.{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 deprecated table field is included for compatibility with existing receivers. Use trigger to select the payload format and handler when building or updating an integration. No removal date is scheduled. A future sunset date will be published here and communicated to API consumers before the field is removed. Plus delivery bookkeeping: tenantId, webhookSubscriptionId, createdAt, updatedAt, scheduledAt, processedAt, retryOfId (points to the original event, never the previous retry; null on the original event), and retries (retry events spawned from this one).

The payload shape varies by trigger

There is no single payload shape. Status-change events deliver a mapped object nested under carrierInvoice; row-level events (*.shipment_updated) deliver raw new/old column snapshots. The robust pattern is to read resourceId from the envelope and call GET /api/rest/carrier_invoices/{resourceId} for a canonical representation — then the payload is just a hint about what changed.
Status-change events (update.carrier_invoice.status.APPROVED / .EXCEPTION) — payload.carrierInvoice with the invoice and its matched relations nested:
Row-level events (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:
Upwell sends Authorization: Basic base64(username:password) using the credentials you configure.
No auth header. Rely on URL secrecy / network controls. Not recommended for production.
Upwell always sends 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’s retryAttempts.
  • What does NOT retry: a 4xx response 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).
  • retryAttempts counts 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 2xx quickly; do slow work asynchronously.
  • Build an idempotent receiver: dedupe on the delivery id, and treat a non-null retryOfId as a redelivery of the original 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.