Gareth Wilson Gareth Wilson

Guide to Sendcloud Webhooks: Features and Best Practices

Published


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

FeatureDetails
ConfigurationSettings > Integrations > Configure, "Webhook feedback enabled" and "Webhook url"
ScopeOne integration; only its parcels and actions
Event fieldaction in the body
Signature headerSendcloud-Signature
SchemeHMAC-SHA256, lowercase hex
Signed contentThe entire raw request body
SecretThe integration's Secret Key or Webhook Signature Key, depending on integration type
Replay windowNone. The body has a timestamp, but it is not a separate signed header
RetriesUp to 10, exponential backoff from 5 minutes, capped at 1 hour between attempts
After retriesFailure logged in the connection issue log
OrderingNot guaranteed; use timestamp
Testing"Test API webhook" button
Source IPsNot published

Common events

Sendcloud documents five events.

actionFires when
parcel_status_changedA parcel created through this integration moves to a different status
return_createdA return is created from a parcel matching the integration
integration_connectedThe integration is created
integration_updatedThe integration is updated (shown as "Integration modified" in Sendcloud's reference)
integration_deletedThe 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_status_change_timestamp, 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

  1. In Sendcloud, go to Settings > Integrations and click Configure on your Sendcloud API integration.
  2. Tick Webhook feedback enabled.
  3. Enter your endpoint in the Webhook url field.
  4. Click Save.
  5. 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-Signature header, 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 timestamp per 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:

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.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.