The header
Every delivery carries:
The timestamp is signed along with the body. That is what stops a captured
payload being replayed at you later: an attacker can resend the bytes, but the
timestamp inside them ages out.
Verifying
1
Take the raw body
The exact bytes, before any JSON parsing. Re-serializing a parsed object
does not reproduce them, and the signature will not match.
2
Parse the header
Split on
,, then on =, to get t and v1. A header you cannot parse is
a rejection.3
Check the timestamp
Reject if
|now - t| is greater than your tolerance. The default is 300
seconds, which is also what the SDK uses.4
Recompute and compare
HMAC-SHA256(secret, t + "." + rawBody), hex encoded, compared in
constant time. A plain === on hex leaks a prefix-match oracle.The signing secret
Each endpoint gets its own secret,whsec_ followed by 48 hex characters.
- It is returned once, when the endpoint is created or when you rotate it. Nothing can retrieve it afterwards.
- It never appears in a list response, and it is never logged.
- Store it the way you store any other credential, and never in client-side code.
Rotating
Rotating mints a new secret and returns it once. A safe order, if you cannot tolerate that gap: register a second endpoint alongside the first, cut traffic over once it is verifying, then delete the old one.What Custral will and will not call
A subscription URL is checked when it is created and again on every delivery attempt, so editing it later to something internal does not get past the guard.
Before each attempt, Custral resolves the hostname, checks every address it
gets back, and then connects only to the addresses it checked. The name is not
looked up a second time, so a DNS record changed between the check and the
connection cannot send the delivery somewhere else.
A redirect is never followed either. Custral does not request the
Location, so
your endpoint cannot bounce a delivery to an address the check did not approve.
See Redirects are not followed.
A refused URL is not retried, since waiting does not make it safe. Fix the URL
or its DNS record, and the next event is checked from scratch.
Handling a failure
Return a4xx on a signature failure and do no work. Custral treats a non-2xx
as a failed delivery and retries it, which is the
correct outcome for a transient problem and harmless for a forged request.