maddoxdocs

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#

  1. Subscribe#

    Register a destination URL under a subscription id you choose. Only url is required; the single supported topic is label.rendered.

    cURL
    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" }'
    Response
    {
      "status": "ok",
      "scope": "tenant",
      "id": "my-wms",
      "version": 1,
      "topics": ["label.rendered"],
      "signing_secret": "whsec_…"
    }
    CautionThe signing_secret is 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.
  2. Receive a Delivery#

    When a template-driven render succeeds (POST /v1/labels/from-data), Maddox POSTs a JSON body to your URL. The data.url is a signed URL to the rendered PNG, valid for one hour.

    delivery
    {
      "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 via POST /v1/labels.
  3. Verify the Signature#

    Every delivery carries headers so you can prove it's authentic:

    HeaderMeaning
    X-Maddox-SignatureTimestamp + HMAC-SHA256 of <t>.<raw-body> as t=…,v1=…
    X-Maddox-Event-IdStable id for idempotency / dedupe
    X-Maddox-TopicThe event topic

    Take the t value and the raw request body, compute HMAC-SHA256( signing_secret, "<t>.<body>" ), and compare the hex digest against v1:

    Python
    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)
  4. Respond 2xx#

    Return any 2xx status 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#

ActionRequest
ListGET /v1/webhooks
Get oneGET /v1/webhooks/:id
Create / updatePOST /v1/webhooks/:id
DeleteDELETE /v1/webhooks/:id
Recent deliveriesGET /v1/webhooks/:id/deliveries