Guide to Sendcloud Webhooks: Features and Best Practices
Sendcloud is a shipping platform for e-commerce. It creates labels across many carriers, tracks parcels and handles returns. Once a parcel is on its way, the useful information is in its status updates. Sendcloud webhooks push those updates to your application, so your order system stays current without polling the API.
This guide covers how Sendcloud webhooks are configured on an integration, how to verify the Sendcloud-Signature header, the five events and their payloads, Sendcloud's retry behaviour, and the best practices for running a Sendcloud receiver in production.
What are Sendcloud webhooks?
A Sendcloud webhook is a URL set on one integration, typically your Sendcloud API integration. Sendcloud sends a JSON body to that URL when something happens to the integration itself, when a parcel created through it changes status, or when a return is created from one of its parcels.
Every request carries a Sendcloud-Signature header: the hex HMAC-SHA256 of the raw body, keyed with your integration's secret. The body's action field names the event.
Webhooks belong to the integration. You only receive events for parcels created through that integration and actions that belong to it, so a second integration needs its own webhook configuration.
Sendcloud webhook features
| Feature | Details |
|---|---|
| Configuration | Settings > Integrations > Configure, "Webhook feedback enabled" and "Webhook url" |
| Scope | One integration; only its parcels and actions |
| Event field | action in the body |
| Signature header | Sendcloud-Signature |
| Scheme | HMAC-SHA256, lowercase hex |
| Signed content | The entire raw request body |
| Secret | The integration's Secret Key or Webhook Signature Key, depending on integration type |
| Replay window | None. The body has a timestamp, but it is not a separate signed header |
| Retries | Up to 10, exponential backoff from 5 minutes, capped at 1 hour between attempts |
| After retries | Failure logged in the connection issue log |
| Ordering | Not guaranteed; use timestamp |
| Testing | "Test API webhook" button |
| Source IPs | Not published |
Common events
Sendcloud documents five events.
action | Fires when |
|---|---|
parcel_status_changed | A parcel created through this integration moves to a different status |
return_created | A return is created from a parcel matching the integration |
integration_connected | The integration is created |
integration_updated | The integration is updated (shown as "Integration modified" in Sendcloud's reference) |
integration_deleted | The integration is deleted |
Every payload has action and timestamp. Sendcloud describes timestamp as a Unix timestamp, and its example value, 1525271885993, is in milliseconds, so check the scale against a real delivery before comparing it with other times.
parcel_status_changed is the event most integrations are built around. Its body adds carrier_, the time the status changed in the carrier's system, and a parcel object holding the full parcel: id, address fields, tracking_number, tracking_url, carrier, is_return, order_number, external_order_id, shipment, date_updated and more. The status is an object with an id and a message:
"status": { "id": 1, "message": "Ready to send" }
There is no status code field. Use parcel.status.id to identify the status; the full list of IDs is available from Sendcloud's parcel statuses endpoint.
return_created carries a data object describing the return: id, order_number, return_reason_id, refund_type, delivery_choice, items and more. It fires for returns created through the API or the Return Portal, but not for returns created manually with the Return Form.
The three integration events carry an integration object with the integration's id, shop_name, shop_url, system, webhook_active, webhook_url and timestamps such as last_updated_at.
Setting up Sendcloud webhooks
- In Sendcloud, go to Settings > Integrations and click Configure on your Sendcloud API integration.
- Tick Webhook feedback enabled.
- Enter your endpoint in the Webhook url field.
- Click Save.
- Optionally click Test API webhook to send a test request to your endpoint.
Find the integration's Secret Key, or its Webhook Signature Key if your integration type has one, and store it server-side, for example as SENDCLOUD_WEBHOOK_SECRET.
If you run several integrations, for example an API integration and a shop plugin, repeat this for each one that creates parcels you need updates for.
Securing Sendcloud webhooks
Compute an HMAC-SHA256 over the entire raw request body, exactly as received, using your secret. Encode it as hex and compare it with Sendcloud-Signature in constant time.
const crypto = require('crypto');
const express = require('express');
function verifySendcloudSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const received = Buffer.from(signatureHeader.trim().toLowerCase(), 'utf8');
const computed = Buffer.from(expected, 'utf8');
return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}
const app = express();
app.post('/webhooks/sendcloud', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.SENDCLOUD_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifySendcloudSignature(req.body, req.get('sendcloud-signature'), secret)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
// Queue the event for processing, keyed on event.action.
res.sendStatus(200);
});
The same check in Python:
import hashlib
import hmac
def verify_sendcloud_signature(raw_body: bytes, signature_header, secret: str) -> bool:
if not signature_header or not secret:
return False
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
received = signature_header.strip().lower().encode("utf-8")
return hmac.compare_digest(received, expected.encode("utf-8"))
# In FastAPI: raw_body = await request.body()
# verify_sendcloud_signature(raw_body, request.headers.get("sendcloud-signature"),
# os.environ["SENDCLOUD_WEBHOOK_SECRET"])
Sendcloud publishes a test vector: the body {"key": "value"} (with the space after the colon) and the secret secretkey give 1eed4b3d41f4653ac64fd56f1bf1cbfd349e4482cbc11dff7134bd93e5da4b0a.
Verification fails if you use the wrong key. Some integration types sign with the Secret Key and others with a separate Webhook Signature Key, so check which one your integration shows.
Make Sendcloud webhooks production-ready. Hookdeck Event Gateway verifies the
Sendcloud-Signatureheader, accepts each parcel update as it arrives, and queues it for your service with full delivery history.
Sendcloud webhook limitations and pain points
Out-of-order updates
The Problem: A parcel shows an older status after a newer one has already arrived.
Why It Happens: Sendcloud says webhooks can arrive out of order, especially after your application has been unreachable and retries catch up.
Workarounds:
- Store the latest
timestampper parcel and ignore any update older than it. - When in doubt, fetch the parcel from Sendcloud's API before updating customer-facing status.
How Hookdeck Can Help: Hookdeck records every delivery with its arrival time and body, so you can see the order in which updates came in and replay them if your state needs rebuilding.
Retries spread over hours
The Problem: After a short outage, parcel updates trickle in for hours.
Why It Happens: Sendcloud retries up to 10 times, starting 5 minutes after the failure and backing off to an hour between attempts. After the last one it stops and logs the failure in the connection issue log.
Workarounds:
- Keep the endpoint highly available and quick to respond.
- After an outage, check the connection issue log and reconcile affected parcels from the API.
How Hookdeck Can Help: Hookdeck keeps accepting Sendcloud's requests while your service is down, then retries towards your service as soon as it recovers, without waiting for Sendcloud's backoff.
Webhooks are per integration
The Problem: Parcels created in one channel never produce webhooks.
Why It Happens: Each integration only sends events for its own parcels. A shop plugin's parcels are not reported through the API integration's webhook.
Workarounds:
- Configure a webhook on every integration that creates parcels you care about, and use a separate path per integration so you know which secret to use.
How Hookdeck Can Help: Each integration can point at its own Hookdeck source with its own secret, and all of them can feed one destination.
No delivery ID
The Problem: A retried update is processed twice.
Why It Happens: Sendcloud sends no delivery or event ID.
Workarounds:
- Key idempotency on
action, the parcelid,parcel.status.idandtimestamp. See our guide to webhook idempotency.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeats based on the body fields you choose.
Best practices
Verify against the raw body
Sendcloud stresses hashing the body exactly as received. Mount express.raw() on the route, use await request.text() in Next.js, or await request.body() in FastAPI.
Route on action, then on status ID
Branch on action first. For parcel_status_changed, map parcel.status.id to your own states rather than parsing the message text.
Treat return parcels separately
parcel.is_return tells you when an update concerns a return. Return parcels only send updates if the outgoing shipment was created through the API integration, so returns from other channels need another source of truth.
Acknowledge fast and process asynchronously
Customer emails and order updates involve external calls. Verify, enqueue and return 200. See why to process webhooks asynchronously.
Conclusion
Sendcloud signs every webhook with a hex HMAC-SHA256 of the raw body in Sendcloud-Signature, keyed with your integration's Secret Key or Webhook Signature Key. Verify the raw bytes, route on action, order updates by timestamp, and configure a webhook on every integration whose parcels you track.
Hookdeck Event Gateway sits in front of your receiver, verifies each Sendcloud request, and holds parcel updates until your service has processed them, so an outage does not leave you waiting on Sendcloud's hourly retries.
Get started with Hookdeck for free and handle Sendcloud webhooks reliably in minutes.