Webhook overview
A webhook is how ReceiptRoller tells your server, with an HTTPS POST to a URL you register, that a sale or an order has happened in a business account. This article covers which events are actually sent, what the request looks like, what delivery does and does not guarantee, and when to use the API instead.
How it works
- You register webhooks per developer app, giving a receiving URL and the events you want. One app can have several webhooks
- A webhook belongs to one business account and receives only the events that happen in that business account
- When an event happens, ReceiptRoller POSTs signed JSON to every active webhook subscribed to it
- A
2xxresponse within 10 seconds is a success. A failure is retried after 1 minute, 10 minutes and 1 hour; if all four attempts fail, the webhook is switched off - The developer API is available on the Starter plan and above
Events that are sent
| Event | Sent when | What data holds |
|---|---|---|
receipt.created |
A store's sale is saved in ReceiptRoller for the first time (connected POS, payment terminal or CSV import). Once per sale: a resend, re-sync or re-import of the same sale is not announced again. Not sent for a deleted sale | A summary of the sale (terminal_id, transaction_id, store_id, receipt_number, issued_at, status, total_amount, tax_amount, currency and more). No customer identifiers |
order.created |
An order is created in order management | The whole order (order_id, order_number, status, total_amount, the orderer's name, contact details and shipping address, items_json and more) |
order.updated |
An order is edited, or its status changes through confirm, start processing, cancel or hand-over at a store POS | For an edit, the whole order. For a status change, only order_id, organization_id and status (plus linked_pos_terminal_id and linked_pos_transaction_id for a POS hand-over) |
test.ping |
Only when you call the test endpoint | { "message": "This is a test ping from Receipt Roller." } |
receipt.updated, receipt.deleted, coupon.redeemed, coupon.expired and customer.visited can also be chosen at registration, but they are not sent. Subscribing to them delivers nothing.
receipt.created is not filtered by the sale's status. A transaction saved for the first time as voided (Voided) or refunded (Refunded) is sent too, so check status on your side if you only want sales.
The request you receive
- The method is
POSTand the body is UTF-8 JSON (Content-Type: application/json) - Every key in the body is snake_case
| Header | Contents |
|---|---|
X-RR-Signature |
sha256= followed by the lower-case hex HMAC-SHA256 of the raw body, keyed with the webhook's secret |
X-RR-Event |
The event name, e.g. receipt.created |
X-RR-Delivery |
The event's ID. The same value as the body's id, and unchanged on retries |
The body looks like this.
{
"id": "evt_1a2b3c4d5e6f",
"event": "receipt.created",
"timestamp": "2026-10-26T03:15:43.1234567Z",
"organization_id": "{organizationId}",
"data": { ... }
}
id: the event ID (evt_plus 12 hex characters). Use it to drop duplicatesevent: the event nametimestamp: when ReceiptRoller sent the event out (UTC). Retries carry the same valueorganization_id: the ID of the business account the event happened indata: the event's contents (see the table above)
What delivery guarantees, and what it does not
- An event can arrive more than once. It is retried when, for example, your server finished processing but did not manage to return
2xx. Drop duplicates byX-RR-Delivery(the body'sid) - Order is not guaranteed. Each event is sent on its own and retries arrive later. An
order.updatedcan arrive before itsorder.created - An event can be lost. Pending retries are held on a ReceiptRoller server, not saved to a queue. If that server restarts while a retry is waiting, the remaining retries do not happen. Events that occur while a webhook is switched off are not held back and are never sent later
- There is no way to resend a past event by hand
So for an integration that must not miss anything, treat the webhook as a trigger and reconcile against the API on a schedule.
Webhook or API
| What you want | Use |
|---|---|
| Know about a new sale or order straight away | Webhook |
| List past sales, or aggregate daily | API (Transactions API) |
| Also need line items or member details | Learn of the sale by webhook, then fetch it from the API using terminal_id and transaction_id |
| Know about corrected or deleted sales, coupon use or visits | API. These events are not sent yet |
| Make sure nothing is missed | Both: receive by webhook, reconcile with the API on a schedule |
Related guides
-
Receive new sales by webhook and post them to SlackRegister a receipt.created webhook, verify its signature and drop duplicates in Node.js, and post each new sale to a Slack channel.
-
Designing resends, ordering, and idempotencyWhat counts as success, the retry rules (1 min, 10 min, 1 h, four attempts), ordering and lost events, and dropping duplicates by X-RR-Delivery.
-
How to register a WebhookRegister a webhook in the developer portal or through the API, choose events that are actually sent, run a test send, and change, pause or delete a webhook.
-
Webhook is not being deliveredWhen a webhook does not arrive: narrow it down with a test send and the delivery log, then check the active flag, subscribed events, connection and signature.
-
Developer Help IndexThe index of ReceiptRoller developer help. Covers the developer application, app registration, OAuth authentication and scopes, implementation guides (wallet apps, store-facing Webhooks, the Survey API), guides by data domain, operations and security, the community, and troubleshooting.