Webhooks
Webhooks deliver real-time shipment status updates to your HTTP endpoint as they occur. This is one of two status relay methods supported by Duty Pro (the other being SFTP).
Configure your webhook endpoint in the dashboard under Settings > Status Relay:
- Select Webhook as the relay method.
- Enter your endpoint URL (must accept
POSTrequests). - Optionally provide a webhook secret for signature verification.
- Optionally provide a basic auth username and password if your endpoint requires HTTP Basic authentication. Both fields must be set together.
Payload format
Section titled “Payload format”Duty Pro sends a POST request with a JSON body. Two examples:
// shipment_created{ "event": "shipment_created", "reference": "ORD-2026-00123", "shipment_status": "PENDING", "test": false, "email_sent": 0, "sms_sent": 0, "held": true, "held_on_error": false, "canceled": false}// payment_completed{ "event": "payment_completed", "reference": "ORD-2026-00123", "shipment_status": "PAID", "test": false, "email_sent": 1, "sms_sent": 1, "held": false, "held_on_error": false, "canceled": false}Payload fields
Section titled “Payload fields”| Field | Type | Description |
|---|---|---|
event | string | The event key that triggered this delivery (e.g. shipment_created, payment_completed) |
reference | string | Your shipment reference number |
shipment_status | string | The shipment’s current payment status (PENDING, PAID, FAILED, or EXPIRED) |
test | boolean | true for shipments created with a test API key (dp_test_...), false for live shipments |
email_sent | number | Number of emails successfully sent to the consignee for this shipment |
sms_sent | number | Number of SMS messages successfully sent to the consignee for this shipment |
held | boolean | true when the shipment is held and customs payment is required |
held_on_error | boolean | true if the shipment is held because of a calculation error |
canceled | boolean | true if the payment has expired |
Events
Section titled “Events”The full list of available events is shown in the dashboard under Settings > Status Relay. From that screen you can also choose which events should be relayed; unchecked events are skipped.
Custom field names
Section titled “Custom field names”If your receiving system expects different field names, contact the Duty Pro team to configure custom key mappings for your account. For example, reference can be mapped to barcode or any other name your system requires.
Headers
Section titled “Headers”Every webhook request includes the following headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-DutyPro-Signature | HMAC-SHA256 signature (only if a webhook secret is configured) |
Authorization | Basic <base64(username:password)> (only if basic auth credentials are configured) |
Signature verification
Section titled “Signature verification”If you configured a webhook secret, every request includes an X-DutyPro-Signature header containing an HMAC-SHA256 signature. Verify it to ensure the request is authentic and hasn’t been tampered with.
Verification process
Section titled “Verification process”- Extract the
X-DutyPro-Signatureheader value. - Serialize the request body as JSON with keys sorted alphabetically.
- Compute HMAC-SHA256 using your webhook secret as the key and the serialized body as the message.
- Compare the computed signature with the header value.
Example (Python)
Section titled “Example (Python)”import hmacimport hashlibimport json
def verify_signature(payload: dict, secret: str, signature: str) -> bool: body = json.dumps(payload, sort_keys=True).encode() expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature)Example (Node.js)
Section titled “Example (Node.js)”const crypto = require('crypto');
function verifySignature(payload, secret, signature) { const body = JSON.stringify(payload, Object.keys(payload).sort()); const expected = crypto .createHmac('sha256', secret) .update(body) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signature) );}Retry behavior
Section titled “Retry behavior”The first delivery attempt is sent immediately when the event occurs: there is no scheduling delay before the first call.
If your endpoint returns a non-success HTTP status (anything outside 200-299) or the request times out (15-second timeout), Duty Pro retries with exponential backoff:
| Attempt | Timing |
|---|---|
| 1 (initial) | Immediate |
| 2 (1st retry) | 1 minute after attempt 1 fails |
| 3 (2nd retry) | 2 minutes after attempt 2 fails |
| 4 (3rd retry) | 4 minutes after attempt 3 fails |
| 5 (4th retry) | 8 minutes after attempt 4 fails |
After 5 failed attempts, the relay is marked as FAILED. You can view failed deliveries in the dashboard under Settings > Status Relay logs, or in the shipment detail view.
Responding to webhooks
Section titled “Responding to webhooks”Your endpoint should:
- Return a
2xxstatus code to acknowledge receipt. - Respond within 15 seconds.
- Process the event idempotently; the same event may be delivered more than once during retries.
Monitoring
Section titled “Monitoring”All webhook delivery attempts (successful and failed) are logged and visible in two places:
- Shipment detail view: the “Status Relay Log” section shows relay events for that specific shipment, including payload, response code, and error messages.
- Settings > Status Relay: shows the overall relay configuration and recent delivery history.