Webhook Signature Schemes Across 10 Shipping APIs
We checked webhook signature methods on 10 shipping/carrier platforms as of October 2026: algorithms, headers, replay windows, and what's undocumented.
Pull the webhook security page from ten shipping and multi-carrier platforms and you get ten different answers to one question: how do you know the POST hitting your endpoint actually came from the vendor? This post puts the actual figures side by side, pulled directly from each vendor's own developer documentation (with one exception, flagged explicitly), collected on 14 October 2026. No field is estimated. Where a vendor doesn't publish something, the table says so.
Why the signature scheme matters more than it looks
A multi-tenant webhook receiver sitting behind 1,000+ shippers is, by default, an open POST target. If a platform ships no signature, anyone who guesses or leaks your endpoint URL can write a fake tracking update, a fake label event, or a fake refund into your system, and your routing layer has no cryptographic way to tell the difference between that and a genuine carrier event. This is not theoretical: it is the documented default behaviour of at least one platform in this set.
The benchmark table
Eight platforms were checked against their current public developer documentation. Two signature schemes dominate: HMAC-SHA256 over a shared secret, and RSA-SHA256 verified against a published JWKS. The gap between them is less about the algorithm and more about whether a timestamp is bound into the signed payload at all.
| Platform | Algorithm | Header(s) | Replay tolerance | Provisioning | Source |
|---|---|---|---|---|---|
| EasyPost | HMAC-SHA256 | X-Hmac-Signature (legacy) / x-timestamp, x-path, x-hmac-signature-v2 | Configurable, 1 minute documented; requests >30s in the future rejected | Default once a webhook_secret is set | EasyPost HMAC Validation |
| ShipEngine / ShipStation V2 | RSA-SHA256 + JWKS | x-shipengine-rsa-sha256-key-id, x-shipengine-rsa-sha256-signature, x-shipengine-timestamp | 5 minutes (documented example) | Default, key rotation via JWKS | ShipEngine Setting Up Webhooks |
| ShipStation V1 (legacy) | None | None | Not applicable | Not applicable | Hookdeck ShipStation guide |
| Shipwell | HMAC-SHA256 | X-Shipwell-Signature, X-Shipwell-Timestamp | Not published | Default on webhook creation | Shipwell Use Webhooks |
| Shippo | HMAC-SHA256 (hex) | Shippo-Auth-Signature (t=…,v1=…) | Not published officially (third-party implementations use 5 min) | Manual, via solutions team / account manager | Shippo Webhook Security |
| Sendcloud | HMAC-SHA256 | Sendcloud-Signature | Not published (timestamp included for ordering, not stated as replay control) | Default when a secret key is configured | Sendcloud Webhooks overview |
| AfterShip | HMAC-SHA256 (base64) | aftership-hmac-sha256 (Tracking); am-webhook-signature (Shipping); as-signature-hmac-sha256 (Returns/Warranty) | Not published | Default from v4.3 onward | AfterShip Webhook Signature |
| Cargoson | Not published | Not published | Not published | Not published | No public developer docs found |
EasyPost's HMAC validation process verifies the integrity of the request body, prevents replay attacks using a timestamp tolerance window, and returns clear error codes when validation fails, in a dual-layer approach that ensures both authenticity and freshness of webhook requests. Requests older than the configured tolerance are rejected, and requests more than 30 seconds in the future are rejected. That's a tighter clock-skew allowance than most vendors publish. A webhook secret is included when creating or updating a webhook, and EasyPost generates a signature with this secret, sent via the X-Hmac-Signature header in each event.
On the RSA side, ShipStation includes a digital signature (RSA-SHA256) in all outgoing webhooks, and if the three signature headers aren't present, you should respond with 404 and stop processing, which helps hide the existence of your webhook endpoint from anyone attempting to impersonate the service. The published reference implementation checks a 5-minute window against the x-shipengine-timestamp header before even touching the signature, and retrieves the public key by fetching the JSON Web Key Set from the public endpoint, matching the kid in the header against the key set.
What "Not published" actually means here
Two different things hide behind that label. For Shipwell, Sendcloud and AfterShip, the signing mechanism is real and documented, but no tolerance window is stated anywhere in the docs reviewed. That's a gap you fill yourself, not a vendor failure. For Shippo, the mechanism exists but isn't self-serve: to set up HMAC for webhooks you email your account manager or contact the sales team. One open-source integration project that went through this process documented the requirement that Shippo's solutions team provision a secret, taking up to roughly ten business days, a figure worth flagging as third-party experience rather than an official Shippo SLA, since Shippo itself doesn't publish a turnaround commitment.
For Cargoson, the gap is different again: no public webhook signing documentation was found at all. That puts it alongside a broader pattern among mid-market TMS and middleware vendors such as nShift, Descartes and MercuryGate, which commonly negotiate webhook security bilaterally with enterprise customers rather than publishing a spec. If you're evaluating any of these, ask the question directly during procurement rather than assuming parity with the documented vendors above.
The no-signature-by-default case: ShipStation V1
ShipStation's V1 webhooks use a thin-payload model and have no signature verification at all. The trust model is unusual: V1 offers no signature or HMAC, and authenticity rests on the secrecy of your endpoint URL plus the fact that you fetch the real data from an authenticated ShipStation API URL. That second half is the actual defence: a thin payload announces that something changed, and your code is expected to re-fetch the real resource over an authenticated call before trusting it. That's a pattern worth generalising:
- Never act on payload fields directly if a platform has no signing guarantee; treat the webhook as a pointer, not as data.
- Re-fetch the resource via an authenticated API call using credentials the forger cannot have.
- Rate-limit and timing-safe-compare any URL token used as a secondary control, since that's often the only gate left.
- Log every unsigned delivery with source IP and timestamp, because it's your only forensic trail when there's no cryptographic one.
Replay protection: who actually ships a tolerance window
EasyPost and ShipEngine are the two platforms in this set that publish a concrete, enforced timestamp window as part of their own verification logic. Everyone else either doesn't bind a timestamp into the signed string at all, or does so without stating how stale is too stale. Sendcloud is a partial case: a timestamp is included specifically because webhook arrival order can be scattered, which is useful for sequencing but isn't documented as a replay defence. The practical takeaway for an architect: if the vendor doesn't publish a tolerance, assume none exists, and make idempotency-key deduplication on your side the real replay defence rather than trusting the transport layer to catch a resend.
Building a receiver that doesn't hardcode any of this
With eight schemes and three header conventions in one receiver, hardcoding verification logic per carrier is a maintenance trap. Treat algorithm, header names and tolerance as config, not code:
{
"easypost": { "algo": "hmac-sha256", "headers": ["x-hmac-signature-v2","x-timestamp","x-path"], "toleranceSec": 60 },
"shipengine": { "algo": "rsa-sha256-jwks", "headers": ["x-shipengine-rsa-sha256-signature","x-shipengine-rsa-sha256-key-id","x-shipengine-timestamp"], "toleranceSec": 300 },
"shippo": { "algo": "hmac-sha256-hex", "headers": ["shippo-auth-signature"], "toleranceSec": null },
"shipstation_v1": { "algo": "none", "verify": "refetch-with-basic-auth" }
}
One verifier function reads the config for the source, dispatches to the right algorithm implementation, and falls back to the refetch pattern when algo is "none". Adding a new carrier platform, whether that's Cargoson, ShipEngine or EasyPost, becomes a config entry rather than a new code path.
Before go-live, ask the vendor three questions
- Is there a published signature scheme, and is the algorithm, header name and signed-string format documented publicly, not just mentioned in a support thread?
- Is there a documented replay tolerance window, and does the vendor's own reference code enforce it, or is that left entirely to you?
- Is signing on by default, or does it require manual provisioning, and if manual, what's the actual turnaround once you've asked?
Run those three questions against Cargoson, EasyPost, ShipEngine, Shippo, Sendcloud and AfterShip before you write a single line of verification code, and build your receiver so that the answer "not published" for any of them doesn't block the integration, only changes which defensive pattern (refetch, your own idempotency window, IP allowlisting) carries the actual trust burden.