Skip to main content

Overview

Webhooks push order-lifecycle events to your systems as they happen, so you can start fulfillment without polling GET /provider/orders. Register an HTTPS endpoint, subscribe it to the event types you care about, and Offergrid POSTs a signed JSON envelope to it every time one of those events occurs on an order that includes one of your offers.
Every delivery attempt — success or failure — is recorded and queryable via GET /provider/webhooks/{id}/deliveries. That log is the first place to look when something appears to be missing.

Registering a webhook

Create the webhook with the Provider API. The response contains the signing secret, and it is shown only once — store it before you discard the response.
The url must be https:// — plain HTTP is rejected at validation.
To pause deliveries without losing the webhook or its secret, PATCH it with {"isActive": false} rather than deleting it.

Event types

These are the six event types you can subscribe to. Subscribing to a type not on this list is rejected.
order.item.created is the one to build fulfillment on. It carries the specific item you need to fulfill; order.created describes the order as a whole, which may span several providers.order.consent_recorded is the record to keep before you dial: it carries the exact disclosure text the customer saw, the number it binds to, and whether marketing calls/texts were granted. order.consent_revoked is the signal to stop — honour it within ten business days. See Contact consent.Resellers subscribe to these same types on their own endpoint and receive them scoped to the orders they placed — see Reseller webhooks. If your team is hybrid, one webhook covers both roles and each event is delivered exactly once.

Payload structure

Every delivery is a POST with Content-Type: application/json and this envelope:
Payloads snapshot order-time data — offer name, price, address, customer info — rather than referencing live rows, so a delivery stays accurate even if the offer changes afterwards.

order.item.created

order.item.status_changed

from and to are the item’s statuses; orderStatus is the parent order’s status recomputed from all of its items.

order.created

order.cancelled

One event per consent decision that names your team. kind is order_contact (transactional — you may contact the customer about this order) or marketing_calls_texts (TCPA prior express written consent for autodialed/prerecorded marketing calls and texts). A declined marketing decision is delivered too, with granted: false.
Same payload as order.consent_recorded, plus revokedAt and revokedVia (web_form for offergrid.io/opt-out, provider_report when another party reported a STOP through the API). Treat the number as do-not-contact for marketing from this moment; you may still contact the customer to fulfil an order they placed.

Verifying signatures

Signed deliveries carry a single header:
There is no separate timestamp header — the timestamp lives in the t= component and is also part of the signed payload, so a verifier can reject stale or replayed deliveries without any shared clock beyond a tolerance window. The signature is HMAC-SHA256 over the string `${t}.${rawBody}`, keyed with your webhook secret, hex-encoded.
Sign over the raw request body, not a re-serialized copy. JSON.parse followed by JSON.stringify can reorder keys or change number formatting, and the signature will not match. Capture the raw bytes before body parsing.
One case is unsigned: if an offer has a submissionUrl and your team has no registered webhook at all, order.item.created is delivered to that URL without a signature, because there is no secret to sign with. Register a webhook — even an inactive one — to get signed submissions.

Delivery behaviour

What Offergrid guarantees from the sending side:
Retries are per-event, not per-target. If you have several webhooks subscribed to the same event and one fails, the retry re-sends to all of them — which is another reason to deduplicate on the envelope id.

Responding

Return 2xx as soon as you have durably accepted the event, and do the real work in a background job. You have 10 seconds, but treating that as a budget rather than a target keeps you off the retry path.

Deduplicating

Testing your endpoint

Point a webhook at a tunnel while you develop:
Register the tunnel URL, then place a test order from a reseller account to trigger a real, correctly-signed delivery. This is the only way to exercise signature verification end to end — a hand-rolled cURL request carries no valid signature, so a correct verifier will reject it. To confirm what Offergrid actually sent and what your endpoint returned:

Troubleshooting

  1. Confirm the webhook is isActive: true.
  2. Confirm it subscribes to the event type you expect — a webhook only receives types listed in its events array.
  3. Check the delivery log. If attempts are recorded with a non-2xx status, the problem is on your side; if there are no attempts at all, no matching event was emitted for your team.
  4. Confirm the order actually contains one of your offers — item-scoped events only go to the provider that owns the item.
  1. Sign the raw body, not a re-serialized copy.
  2. Parse the timestamp out of the t= component of X-Offergrid-Signature. There is no x-offergrid-timestamp header.
  3. The signed string is `${t}.${rawBody}` — the timestamp, a literal dot, then the body.
  4. Compare hex-decoded buffers, and check lengths first: Node’s timingSafeEqual throws on a length mismatch.
  5. Confirm you stored the secret from the creation response — it is shown only once and cannot be retrieved later.
A null status code means the request never completed — DNS failure, TLS error, connection refused, or your endpoint exceeded the 10-second timeout. The recorded response body holds the underlying error message.
Expected. Delivery is at-least-once, and a retry re-sends to every subscribed target. Deduplicate on the envelope id.

Next steps

Receiving Orders

What to do with an order once the webhook lands

Order Workflow

The status lifecycle behind order.item.status_changed

Provider API Reference

Every provider endpoint, including webhook management

API Integration

Broader integration patterns