Webhooks
A webhook is a callback. Instead of you polling the API to ask "did that label render yet?", the API calls you — it POSTs to a URL you registered the moment a label is rendered.
What#
The analogy: webhooks are the API's doorbell. You give it your address once; it rings you when something happens, rather than you walking to the door every few minutes to check.
How#
Subscribe#
Register a destination URL under a subscription id you choose. Only
urlis required; the single supported topic islabel.rendered.curl -X POST https://api.maddoxapi.dev/v1/webhooks/my-wms \ -H "Authorization: Bearer $MADDOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/maddox" }'{ "status": "ok", "scope": "tenant", "id": "my-wms", "version": 1, "topics": ["label.rendered"], "signing_secret": "whsec_…" }CautionThesigning_secretis returned once, on create. Store it — you'll need it to verify deliveries, and it is never re-surfaced.NoteYour URL must be a public HTTPS endpoint. Internal/loopback targets (localhost,127.0.0.1, the cloud metadata IP) are rejected to prevent server-side request forgery.Receive a Delivery#
When a template-driven render succeeds (
POST /v1/labels/from-data), MaddoxPOSTs a JSON body to your URL. Thedata.urlis a signed URL to the rendered PNG, valid for one hour.{ "event": "label.rendered", "event_id": "evt_2026-06-02T18:30:00Z_a1b2c3d4", "event_version": "1", "delivered_at": "2026-06-02T18:30:01Z", "tenant_id": "tnt_…", "data": { "content_hash": "…64 hex…", "external_ref": "MER-04821", "template_id": "asset_label", "template_version": 3, "url": "https://api.maddoxapi.dev/v1/labels/<hash>?…&sig=…" } }NoteDeliveries fire for template-driven renders (/v1/labels/from-data). They do not fire for ad-hoc raw-ZPL renders viaPOST /v1/labels.Verify the Signature#
Every delivery carries headers so you can prove it's authentic:
Header Meaning X-Maddox-SignatureTimestamp + HMAC-SHA256 of <t>.<raw-body>ast=…,v1=…X-Maddox-Event-IdStable id for idempotency / dedupe X-Maddox-TopicThe event topic Take the
tvalue and the raw request body, computeHMAC-SHA256( signing_secret, "<t>.<body>" ), and compare the hex digest againstv1:import hmac, hashlib def verify(signing_secret: str, sig_header: str, raw_body: bytes) -> bool: parts = dict(p.split("=", 1) for p in sig_header.split(",")) t, v1 = parts["t"], parts["v1"] expected = hmac.new( signing_secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256, ).hexdigest() return hmac.compare_digest(expected, v1)Respond
2xx#Return any
2xxstatus to acknowledge. Non-2xx(or a network error) is treated as a failure; the platform retries with backoff, and exhausted deliveries land in a dead-letter queue the operator can inspect and replay.
Managing Webhooks#
| Action | Request |
|---|---|
| List | GET /v1/webhooks |
| Get one | GET /v1/webhooks/:id |
| Create / update | POST /v1/webhooks/:id |
| Delete | DELETE /v1/webhooks/:id |
| Recent deliveries | GET /v1/webhooks/:id/deliveries |
Webhooks API Reference
Full endpoint shapes for every subscription operation.