Webhook Subscriptions
Get notified when things happen in a store
A webhook subscription tells Leal to POST a JSON payload to your URL whenever an event happens in the store, so you don’t have to poll. Subscribe to a list of events, or to ‘[“*”]` for every event (including ones added later). The full guide, with payload examples and signature verification code, is at www.tryleal.dev/developers/webhooks.
Events
-
customer.created: A customer joined the loyalty program. -
customer.updated: A customer’s name, email, phone, birthday, metadata or marketing and SMS consent changed. -
customer_card.created: A customer was issued a loyalty card. Includes the Apple Wallet and Google Wallet links. -
stamp.earned: Stamps were added to a customer’s card, from a scan, the API or an adjustment. -
stamp.removed: Stamps were taken off a customer’s card by an adjustment. Redeeming a reward sends reward.redeemed instead. -
reward.unlocked: A customer now has enough stamps to redeem a reward. Sent once per reward, when the threshold is crossed. -
reward.redeemed: A customer redeemed a reward.
Payload
Each delivery is an envelope: ‘{“id”: “evt_…”, “type”: “stamp.earned”, “timestamp”: “…”, “account_id”: 1, “data”: {…}}`. id is unique per event and stays the same across retries, so use it to ignore duplicates. customer.updated also carries previous_attributes with the old values of the fields that changed.
Subscriptions created from Zapier (and all subscriptions created before signing was introduced) use ‘payload_format: “flat”`, which sends the data object on its own. The event name is still available in the Leal-Event header. A flat subscription has exactly one event.
Older integrations send and read a single event string. That still works: event sets events to that one event, and responses include event whenever there is exactly one.
Verifying requests
Every delivery is signed using the Standard Webhooks scheme (www.standardwebhooks.com) with the subscription’s secret, via the webhook-id, webhook-timestamp and webhook-signature headers. Any Standard Webhooks library can verify it.
Retries
Respond with any 2xx status within 10 seconds. Anything else, or no response, is retried with increasing delays, up to 10 attempts over about four hours. Responding ‘410 Gone` deletes the subscription. A subscription that has not had a successful delivery for 3 days is disabled (`enabled: false`, `disabled_reason: “failing”`); re-enable it with PATCH once fixed.
Returns every webhook subscription for the store, oldest first. Signing secrets are not included; fetch a single subscription to read its secret.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
account_id
|
Must be a number. | Required |
Store (account) ID |
event
|
Must be a String | Optional |
Only return subscriptions that list this event (or ‘*`) |
Error Codes
| Code | Description |
|---|---|
401
|
Unauthorized – invalid or missing API token |
404
|
Store not found |
Returns
Code: 200
An array of webhook subscriptions
| Name | Type | Required | Description |
|---|---|---|---|
id
|
Must be a Integer | Required |
Unique subscription ID |
account_id
|
Must be a Integer | Required |
Parent store ID |
events
|
Must be an array of String | Required |
Events delivered to this URL, or ‘[“*”]` for every event |
event
|
Must be a String | Required |
The event, when there is exactly one (kept for older integrations) (nil allowed) |
target_url
|
Must be a String | Required |
URL that receives the POST requests |
description
|
Must be a String | Required |
Your own label for the subscription (nil allowed) |
payload_format
|
Must be a String | Required |
|
enabled
|
Must be one of: true, false, 1, 0.
|
Required |
Whether events are being delivered |
disabled_at
|
Must be a String | Required |
ISO 8601 time the subscription was disabled (nil allowed) |
disabled_reason
|
Must be a String | Required |
|
last_delivery_at
|
Must be a String | Required |
ISO 8601 time of the most recent delivery attempt (nil allowed) |
last_delivery_status
|
Must be a Integer | Required |
HTTP status your URL returned on the most recent attempt (nil allowed) |
last_delivery_error
|
Must be a String | Required |
Why the most recent attempt failed (nil allowed) |
created_at
|
Must be a String | Required |
ISO 8601 creation timestamp |
updated_at
|
Must be a String | Required |
ISO 8601 last-update timestamp |
Returns a single subscription, including its signing secret and the result of the most recent delivery.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
account_id
|
Must be a number. | Required |
Store (account) ID |
id
|
Must be a number. | Required |
Webhook subscription ID |
Error Codes
| Code | Description |
|---|---|
401
|
Unauthorized – invalid or missing API token |
404
|
Store or subscription not found |
Returns
Code: 200
A webhook subscription, with its secret
| Name | Type | Required | Description |
|---|---|---|---|
id
|
Must be a Integer | Required |
Unique subscription ID |
account_id
|
Must be a Integer | Required |
Parent store ID |
events
|
Must be an array of String | Required |
Events delivered to this URL, or ‘[“*”]` for every event |
event
|
Must be a String | Required |
The event, when there is exactly one (kept for older integrations) (nil allowed) |
target_url
|
Must be a String | Required |
URL that receives the POST requests |
description
|
Must be a String | Required |
Your own label for the subscription (nil allowed) |
payload_format
|
Must be a String | Required |
|
enabled
|
Must be one of: true, false, 1, 0.
|
Required |
Whether events are being delivered |
disabled_at
|
Must be a String | Required |
ISO 8601 time the subscription was disabled (nil allowed) |
disabled_reason
|
Must be a String | Required |
|
last_delivery_at
|
Must be a String | Required |
ISO 8601 time of the most recent delivery attempt (nil allowed) |
last_delivery_status
|
Must be a Integer | Required |
HTTP status your URL returned on the most recent attempt (nil allowed) |
last_delivery_error
|
Must be a String | Required |
Why the most recent attempt failed (nil allowed) |
created_at
|
Must be a String | Required |
ISO 8601 creation timestamp |
updated_at
|
Must be a String | Required |
ISO 8601 last-update timestamp |
secret
|
Must be a String | Required |
Signing secret ( |
Subscribes a URL to one or more events. The response includes the signing secret; store it to verify deliveries. The URL must be publicly reachable over https.
Events: customer.created, customer.updated, customer_card.created, stamp.earned, stamp.removed, reward.unlocked, reward.redeemed, or ‘*` for all of them.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
account_id
|
Must be a number. | Required |
Store (account) ID |
events
|
Must be an array of String | Optional |
Events to subscribe to, or ‘[“*”]` for every event. Required unless |
event
|
Must be a String | Optional |
A single event to subscribe to. Same as |
target_url
|
Must be a String | Required |
Public https URL that will receive the POST requests |
description
|
Must be a String | Optional |
Your own label, up to 255 characters |
payload_format
|
Must be a String | Optional |
|
enabled
|
Must be one of: true, false, 1, 0.
|
Optional |
Create the subscription disabled by passing false (defaults to true) |
Error Codes
| Code | Description |
|---|---|
401
|
Unauthorized – invalid or missing API token |
404
|
Store not found |
422
|
Validation failed – check `errors` for details |
Returns
Code: 201
The new webhook subscription, with its secret
| Name | Type | Required | Description |
|---|---|---|---|
id
|
Must be a Integer | Required |
Unique subscription ID |
account_id
|
Must be a Integer | Required |
Parent store ID |
events
|
Must be an array of String | Required |
Events delivered to this URL, or ‘[“*”]` for every event |
event
|
Must be a String | Required |
The event, when there is exactly one (kept for older integrations) (nil allowed) |
target_url
|
Must be a String | Required |
URL that receives the POST requests |
description
|
Must be a String | Required |
Your own label for the subscription (nil allowed) |
payload_format
|
Must be a String | Required |
|
enabled
|
Must be one of: true, false, 1, 0.
|
Required |
Whether events are being delivered |
disabled_at
|
Must be a String | Required |
ISO 8601 time the subscription was disabled (nil allowed) |
disabled_reason
|
Must be a String | Required |
|
last_delivery_at
|
Must be a String | Required |
ISO 8601 time of the most recent delivery attempt (nil allowed) |
last_delivery_status
|
Must be a Integer | Required |
HTTP status your URL returned on the most recent attempt (nil allowed) |
last_delivery_error
|
Must be a String | Required |
Why the most recent attempt failed (nil allowed) |
created_at
|
Must be a String | Required |
ISO 8601 creation timestamp |
updated_at
|
Must be a String | Required |
ISO 8601 last-update timestamp |
secret
|
Must be a String | Required |
Signing secret ( |
Changes the URL, events, label or payload format, or turns the subscription off and on. Re-enabling a subscription that was disabled for failing clears its failure state.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
account_id
|
Must be a number. | Required |
Store (account) ID |
id
|
Must be a number. | Required |
Webhook subscription ID |
events
|
Must be an array of String | Optional |
Replaces the list of events, or ‘[“*”]` for every event |
event
|
Must be a String | Optional |
A single event. Same as |
target_url
|
Must be a String | Optional |
Public https URL that will receive the POST requests |
description
|
Must be a String | Optional |
Your own label, up to 255 characters |
payload_format
|
Must be a String | Optional |
|
enabled
|
Must be one of: true, false, 1, 0.
|
Optional |
false to pause deliveries, true to resume them |
Error Codes
| Code | Description |
|---|---|
401
|
Unauthorized – invalid or missing API token |
404
|
Store or subscription not found |
422
|
Validation failed – check `errors` for details |
Returns
Code: 200
The updated webhook subscription
| Name | Type | Required | Description |
|---|---|---|---|
id
|
Must be a Integer | Required |
Unique subscription ID |
account_id
|
Must be a Integer | Required |
Parent store ID |
events
|
Must be an array of String | Required |
Events delivered to this URL, or ‘[“*”]` for every event |
event
|
Must be a String | Required |
The event, when there is exactly one (kept for older integrations) (nil allowed) |
target_url
|
Must be a String | Required |
URL that receives the POST requests |
description
|
Must be a String | Required |
Your own label for the subscription (nil allowed) |
payload_format
|
Must be a String | Required |
|
enabled
|
Must be one of: true, false, 1, 0.
|
Required |
Whether events are being delivered |
disabled_at
|
Must be a String | Required |
ISO 8601 time the subscription was disabled (nil allowed) |
disabled_reason
|
Must be a String | Required |
|
last_delivery_at
|
Must be a String | Required |
ISO 8601 time of the most recent delivery attempt (nil allowed) |
last_delivery_status
|
Must be a Integer | Required |
HTTP status your URL returned on the most recent attempt (nil allowed) |
last_delivery_error
|
Must be a String | Required |
Why the most recent attempt failed (nil allowed) |
created_at
|
Must be a String | Required |
ISO 8601 creation timestamp |
updated_at
|
Must be a String | Required |
ISO 8601 last-update timestamp |
Stops deliveries and deletes the subscription. This cannot be undone.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
account_id
|
Must be a number. | Required |
Store (account) ID |
id
|
Must be a number. | Required |
Webhook subscription ID |
Error Codes
| Code | Description |
|---|---|
401
|
Unauthorized – invalid or missing API token |
404
|
Store or subscription not found |
Returns
Code: 204
No content. The subscription was deleted.
Immediately sends a signed webhook.test event to the subscription’s URL and reports what happened, so you can check your endpoint and signature verification without waiting for real activity. Test events are not retried and do not count towards disabling the subscription.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
account_id
|
Must be a number. | Required |
Store (account) ID |
id
|
Must be a number. | Required |
Webhook subscription ID |
Error Codes
| Code | Description |
|---|---|
401
|
Unauthorized – invalid or missing API token |
404
|
Store or subscription not found |
Returns
Code: 200
The outcome of the test delivery
| Name | Type | Required | Description |
|---|---|---|---|
delivered
|
Must be one of: true, false, 1, 0.
|
Required |
True when your URL responded with a 2xx status |
status
|
Must be a Integer | Required |
HTTP status your URL returned (nil allowed) |
event_id
|
Must be a String | Required |
The |
error
|
Must be a String | Required |
Why the delivery failed (nil allowed) |
Replaces the subscription’s signing secret. Deliveries are signed with the new secret straight away, so update your receiver at the same time.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
account_id
|
Must be a number. | Required |
Store (account) ID |
id
|
Must be a number. | Required |
Webhook subscription ID |
Error Codes
| Code | Description |
|---|---|
401
|
Unauthorized – invalid or missing API token |
404
|
Store or subscription not found |
Returns
Code: 200
The webhook subscription, with its new secret
| Name | Type | Required | Description |
|---|---|---|---|
id
|
Must be a Integer | Required |
Unique subscription ID |
account_id
|
Must be a Integer | Required |
Parent store ID |
events
|
Must be an array of String | Required |
Events delivered to this URL, or ‘[“*”]` for every event |
event
|
Must be a String | Required |
The event, when there is exactly one (kept for older integrations) (nil allowed) |
target_url
|
Must be a String | Required |
URL that receives the POST requests |
description
|
Must be a String | Required |
Your own label for the subscription (nil allowed) |
payload_format
|
Must be a String | Required |
|
enabled
|
Must be one of: true, false, 1, 0.
|
Required |
Whether events are being delivered |
disabled_at
|
Must be a String | Required |
ISO 8601 time the subscription was disabled (nil allowed) |
disabled_reason
|
Must be a String | Required |
|
last_delivery_at
|
Must be a String | Required |
ISO 8601 time of the most recent delivery attempt (nil allowed) |
last_delivery_status
|
Must be a Integer | Required |
HTTP status your URL returned on the most recent attempt (nil allowed) |
last_delivery_error
|
Must be a String | Required |
Why the most recent attempt failed (nil allowed) |
created_at
|
Must be a String | Required |
ISO 8601 creation timestamp |
updated_at
|
Must be a String | Required |
ISO 8601 last-update timestamp |
secret
|
Must be a String | Required |
Signing secret ( |