Skip to main content
Webhooks allow you to receive real-time HTTP notifications when events occur on your orders or returns. Instead of polling the API for updates, configure a webhook URL to receive automatic notifications.

Configuration

Configure your webhook URL in the Zinc dashboard under Settings. You can also generate a webhook secret for signature verification.

Webhook Secret

Your webhook secret is used to verify that incoming webhook requests are from Zinc. The secret format is:
Keep your webhook secret secure. If compromised, rotate it immediately in the dashboard. Rotating the secret invalidates the previous one.

Events

Order events

Return events

Return events carry an additional return_id field alongside order_id so you can route by return without parsing the data object. The status field on these events reflects the return-request status (not the order status).

Wallet events

Wallet events aren’t about an order, so they use a smaller payload with no order_id, return_id, or top-level status. See Wallet event payloads.

Payload Structure

All webhook payloads follow this structure:

Payload Fields

Event-Specific Data

order.placed includes price components:
order.failed includes error information:
Tracking events (order.tracking_received, order.shipped, order.available_for_pickup, order.delivered) all carry data.tracking_numbers. Each entry has: On these events the top-level status is the order’s status, which stays order_placed once the order is placed. Shipment progress is reported by the event itself and by each tracking number’s status. order.tracking_received includes the tracking numbers we just received:
order.shipped fires once per order, when the first package starts moving through the carrier network. It lists every tracking number on the order so far:
order.estimated_delivery_updated fires when the retailer revises a delivery estimate you’ve already been given, whether it slips or moves earlier. The first estimate on an order doesn’t fire it. Today it’s sent for Amazon Business orders only. estimated_delivery and previous_estimated_delivery are passed through as the retailer reports them: either a date (2026-01-19) or an ISO 8601 timestamp (2026-01-19T03:00:00Z):
order.available_for_pickup fires for one package each time it moves into available_for_pickup. A multi-package order can send several. available_at is when the package first became available in its current pickup run, and location is the carrier facility, when the carrier reports it. It isn’t sent when nothing on the order can still reach the shopper (for example, a cancelled order):
order.delivered fires when every package on the order has been delivered. Each tracking number adds delivered_at and delivery_proof_url (the carrier’s proof-of-delivery photo, or null). delivered_at may be missing when the retailer, rather than the carrier, reported the delivery:
order.cancelled fires when the retailer cancels the order after placement. The order’s wallet hold is refunded at the same time:
return.created fires when a customer files a return against one of your orders. The data object echoes the return reason, free-text notes, and the line items being returned:
return.label_uploaded fires when a printable mail-back label is attached to the return, which can happen before, at the same time as, or after approval. It fires again if the label is replaced. label_url is a signed link you can fetch straight from the payload. It stops working at label_url_expires_at (7 days), and it’s null if signing failed. In that case, fetch the label from GET /returns/{return_id}. kind is label_pdf or package_label:
return.approved fires when the return is approved. Where applicable, the data object carries the merchant-issued return id and a printable label URL so you can hand them to the customer:
return.denied fires when the return is denied (e.g. outside the retailer’s return window). merchant_return_id and external_label_url are null on denied returns:
return.credited fires when the return is resolved by crediting the customer’s wallet rather than working a merchant RMA. There’s no carrier label or merchant return id in this flow:

Wallet event payloads

Wallet events report the top-up amount in cents and its outcome. error is included on failed and disabled:

Security

Webhook requests include headers for verification:

Verifying Signatures

To verify a webhook is from Zinc, compute the HMAC-SHA256 signature of the raw request body using your webhook secret and compare it to the X-Webhook-Signature header. Python Example:
Node.js Example:
Always verify webhook signatures before processing the payload to ensure the request originated from Zinc.

Best Practices

  1. Respond quickly - Return a 2xx status code as soon as possible. Process the webhook asynchronously if needed.
  2. Handle duplicates - Webhooks may occasionally be delivered more than once. Deduplicate on event together with order_id (or return_id for return events). Wallet events have neither, so use event and timestamp.
  3. Verify signatures - Always validate the X-Webhook-Signature header before trusting the payload.
  4. Use HTTPS - Configure an HTTPS endpoint to ensure webhook data is encrypted in transit.
  5. Log events - Keep records of received webhooks for debugging and auditing.