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.

Rolling out: Webhook management is live, and automatic delivery is switched 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, and inventory.stock_adjustment. Platform events: tenant.provisioned, tenant.suspended, tenant.reactivated. Billing and data-import events are built and being switched on in stages. Stock-level changes, low-stock alerts and expiry warnings are not available and have no announced date — for those, poll the REST API. The event list in the Lift portal is the source of truth for what delivers today; a type you can subscribe to there is live, and anything else is not yet. You can also send signed test deliveries at any time to exercise your receiver end-to-end.

How Webhooks Work

  1. You register a webhook URL in the Lift portal (Webhooks in the left navigation)
  2. When a subscribed event occurs, Lift sends an HTTP POST to your URL
  3. Your endpoint processes the payload and returns a 2xx response
  4. 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). During the rollout, being subscribed to an event type does not yet mean it delivers — emission is being enabled per event type, and a subscribed endpoint starts receiving an event type once emission for it goes live.

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.

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:

  1. Read the X-OpenBoxes-Timestamp header value
  2. Concatenate it with the raw request body, separated by a single .
  3. Compute HMAC-SHA256 of that string with your whsec_… secret
  4. Hex-encode the result, prefix with v1=, and compare with X-OpenBoxes-Signature using a constant-time comparison
  5. 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 id field 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.