Webhooks
Webhooks let your external systems receive notifications when events occur in your OpenBoxes Lift account. Instead of polling the API for changes, you register a webhook URL and Lift pushes signed event data to your endpoint.
What delivers today: Webhook management is live, and automatic delivery is on for eleven event types. From your OpenBoxes instance, typically within a couple of minutes:
order.created,order.approved,order.received,order.cancelled,order.updated,shipment.created,shipment.shipped, andinventory.stock_adjustment. Platform events:tenant.provisioned,tenant.suspended,tenant.reactivated. Billing events, data-import events, stock-level changes, low-stock alerts and expiry warnings are not available — for those, poll the REST API. The event list in the Lift portal is the source of truth for what delivers; every type you can subscribe to there is live. You can also send signed test deliveries at any time to exercise your receiver end-to-end.
How Webhooks Work#
- You register a webhook URL in the Lift portal (Webhooks in the left navigation)
- When a subscribed event occurs, Lift sends an HTTP POST to your URL
- Your endpoint processes the payload and returns a
2xxresponse - If delivery fails, Lift retries on a fixed backoff schedule (see below)
OpenBoxes Lift Your Server
│ │
│ Event occurs │
│ │
├──── POST /your/webhook/url ───>│
│ { id, type, created, │
│ data } │
│ │
│<──── 200 OK ──────────────────│
│ │
Delivery Semantics#
Once an event is accepted by the webhook pipeline, delivery to each subscribed endpoint is at-least-once: every event carries a stable id, and your consumer should deduplicate on it, because the same event can be delivered more than once (for example around a retry or a crash boundary). Events may occasionally be dropped before acceptance — emission never blocks or fails the business action that caused it.
Webhooks are a notification channel, not a ledger. Use them to react promptly, and reconcile authoritative state against the REST API rather than reconstructing it from webhook history.
Event Types#
The event types you can subscribe to are listed on the Webhooks page in the portal (the create dialog and the GET /api/v1/webhooks/event-types endpoint read the same registry). The registry only exposes types that deliver, so a subscribed endpoint receives every event of that type once it occurs in your instance.
Payload Format#
Every delivery wraps the event in the same envelope:
{
"id": "evt-abc123def456",
"type": "tenant.provisioned",
"created": 1753488000,
"data": {
"tenantId": "acme",
"status": "ACTIVE"
}
}
Payload Fields#
| Field | Type | Description |
|---|---|---|
id |
string | Stable event ID — deduplicate on this |
type |
string | Event type identifier |
created |
number | Unix timestamp (seconds) when the delivery was sent |
data |
object | Event-specific data (the affected resource) |
Configuring Webhooks#
Webhooks are managed in the Lift portal under Webhooks (left navigation), not in account settings.
| Setting | Description |
|---|---|
| URL | Endpoint to receive webhook POST requests — use HTTPS |
| Events | Which event types to subscribe to |
| Secret | Generated whsec_… secret used to sign every delivery (regenerate any time) |
| Custom headers | Optional headers added to every delivery |
| Active | Enable or disable the webhook |
Requests to private or internal network addresses are blocked. Always use HTTPS endpoints; plain http:// URLs are deprecated and will stop being accepted.
Plan Caps#
| Plan | Webhook endpoints | Deliveries per hour |
|---|---|---|
| Shared | 10 | 1,000 |
| Dedicated | 50 | 25,000 |
| Enterprise | 100 | 50,000 |
The delivery cap is account-wide across all of your endpoints. Deliveries beyond the hourly cap are not sent, so size subscriptions to the events you actually consume.
Signature Verification#
Every delivery includes two headers:
X-OpenBoxes-Signature: v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-OpenBoxes-Timestamp: 1753488000
The signature is HMAC-SHA256, computed over the string "<timestamp>.<raw request body>" using your webhook secret, hex-encoded, and prefixed with v1=.
To verify:
- Read the
X-OpenBoxes-Timestampheader value - Concatenate it with the raw request body, separated by a single
. - Compute HMAC-SHA256 of that string with your
whsec_…secret - Hex-encode the result, prefix with
v1=, and compare withX-OpenBoxes-Signatureusing a constant-time comparison - Reject requests whose timestamp is more than 5 minutes from your current time (replay protection)
# Example: verify a webhook signature
signed_payload="${TIMESTAMP}.${REQUEST_BODY}"
expected="v1=$(echo -n "$signed_payload" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | cut -d' ' -f2)"
if [ "$expected" = "$RECEIVED_SIGNATURE" ]; then
echo "Signature valid"
fi
Retry Policy#
If your endpoint does not return a 2xx response, Lift retries delivery on a fixed schedule:
| Attempt | Delay after previous failure |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 15 minutes |
| 4th retry | 1 hour |
| 5th retry | 4 hours |
The delays are minimums: retries are picked up by a background scheduler, so an attempt can run slightly later than the scheduled time. After 5 failed retries the delivery is marked exhausted. Delivery history — including status, HTTP response codes, and timing — is visible per webhook in the portal.
Testing Your Endpoint#
Use Send Test Event on a webhook in the portal to trigger a fully signed delivery to your endpoint — the same code path used for real events. You can also send a realistic sample of any active event type. A successful test proves your endpoint is reachable and returning 2xx, and gives you a genuine signed request to develop and check your signature verification against.
No public endpoint yet? Create a test receiver on the portal's Webhooks page: a URL you can register a webhook against, whose deliveries are captured in the portal — signed exactly like production — instead of leaving the platform. Receivers expire after 24 hours.
Manual retry: failed or exhausted deliveries can be retried from the delivery log. Each retry is a new, freshly signed attempt (so your replay-protection window accepts it), linked to the original in the log.
Best Practices#
- Return 2xx quickly: Process webhook payloads asynchronously. Acknowledge immediately and handle the event in a background job.
- Handle duplicates: Use the
idfield to deduplicate. At-least-once delivery means the same event can arrive more than once. - Verify signatures: Always validate
X-OpenBoxes-Signature(with the timestamp check) so you only act on requests that genuinely came from Lift. - Use HTTPS: Terminate TLS at your receiver; internal/private addresses are rejected.
- Monitor failures: Check the delivery history in the portal and investigate endpoint availability issues promptly.