Webhook returns 401 Unauthorized — what to check
A 401 from the webhook endpoint means one of three things failed: the timestamp is missing/old, the signature is missing/wrong, or the body was modified after signing. Run this checklist in order — the first match is almost always the cause.
1. Is the X-Webhook-Timestamp header present and numeric?
Send Unix epoch seconds, not an ISO date, not milliseconds. A string like "2026-05-22T14:00:00Z" will be rejected as malformed.
2. Is your gateway's clock correct?
Requests where the timestamp differs from server time by more than 5 minutes are rejected as potential replays. NTP-sync the gateway. This is the single most common 401 cause on 4G gateways installed in shielded basements.
3. Is the signature format correct?
The X-Webhook-Signature header value must start with "sha256=" followed by lowercase hex. Just the hex without the prefix is rejected.
4. Are you signing the right string?
You sign "<timestamp>.<body>" — the timestamp you put in the X-Webhook-Timestamp header, a literal period, then the raw body bytes. Signing just the body alone is the most common signing mistake — it worked under older docs but no longer.
5. Did the body change after signing?
If your gateway serializes JSON twice (signs one version, sends a re-serialized one with different whitespace or key order) the signatures won't match. Sign and send the exact same bytes — usually that means computing the signature over the already-serialized buffer.
6. Is the secret correct?
The secret is shown once at data-source creation. If you lost it, the only fix is to delete the data source and create a new one — the secret cannot be retrieved later.
Verifying locally before involving support: make the same POST with curl using the documented headers. If curl works, the issue is on the gateway side. If curl also returns 401, the secret or signing logic is wrong.