whsec_.
Headers on every attempt
Only the signature proves that a request came from Surfais. Do not authenticate on
User-Agent or on the sender’s address.
How the signature is computed
X-Surfais-Signature: v1=<signature>.
To verify a request, make these checks in this order and reject the request as soon as one fails:
X-Surfais-SignatureandX-Surfais-Timestampare both present.- The timestamp is a plain run of digits.
- The timestamp is within the tolerance of your own clock, in either direction.
- The signature starts with
v1=, and the rest is exactly 64 lowercase hexadecimal characters. - The signature you compute over the raw bytes you received equals the one presented. Compare in constant time.
401 when a check fails.
Node.js receiver
A complete Express receiver.express.raw() keeps the body as the Buffer that arrived on the wire.
receiver.mjs
Python receiver
The same checks in the same order, with Flask.request.get_data() returns the body as the bytes that arrived.
receiver.py
Test your implementation
Check your own verifier against this known answer before you connect it to live traffic.
The body has no spaces and no trailing newline. The timestamp is in the past, so set your clock argument to a value near it when you run the check. Your verifier must accept this request. It must reject it when you change one byte of the body, use a different secret, or move your clock more than the tolerance away from the timestamp.
Common pitfalls
The timestamp and the signature change on every attempt, because each attempt is signed when it is sent. The body and
X-Surfais-Event-Id do not change. Verify each attempt against its own headers.Respond fast
Answer with a2xx status within 10 seconds, then do your processing. That budget covers the whole attempt, including connecting to your server.
- Only the status code matters. Any
2xxcounts as delivered. The response body is ignored, apart from the first bytes, which are kept in your delivery log. - Any
3xxis a failure. Surfais never follows a redirect with a signed body. Point the endpoint URL at its final location. - Anything else is a failure too. A
4xx, a5xx, a timeout or a connection error is retried on the retry schedule.