Overview
Webhooks push order-lifecycle events to your systems as they happen, so you can start fulfillment without pollingGET /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.url must be https:// — plain HTTP is rejected at validation.
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 withContent-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
order.consent_recorded
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.
order.consent_revoked
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: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.
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: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:Troubleshooting
No deliveries arriving
No deliveries arriving
- Confirm the webhook is
isActive: true. - Confirm it subscribes to the event type you expect — a webhook only
receives types listed in its
eventsarray. - 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.
- Confirm the order actually contains one of your offers — item-scoped events only go to the provider that owns the item.
Signature verification always fails
Signature verification always fails
- Sign the raw body, not a re-serialized copy.
- Parse the timestamp out of the
t=component ofX-Offergrid-Signature. There is nox-offergrid-timestampheader. - The signed string is
`${t}.${rawBody}`— the timestamp, a literal dot, then the body. - Compare hex-decoded buffers, and check lengths first: Node’s
timingSafeEqualthrows on a length mismatch. - Confirm you stored the secret from the creation response — it is shown only once and cannot be retrieved later.
Deliveries recorded as failed with no status code
Deliveries recorded as failed with no status code
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.
Receiving the same event more than once
Receiving the same event more than once
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_changedProvider API Reference
Every provider endpoint, including webhook management
API Integration
Broader integration patterns