Webhook Security
Why it matters
Your webhook address is a public URL. Anyone who finds it can send it a fake payment notification.
Checking the signature is how you tell a real call from a fake one. Do not skip it, and never treat an unverified call as proof of payment.
What Brykto sends
Every webhook carries two headers.
| Header | What it holds |
|---|---|
X-Brykto-Signature |
sha256= followed by the signature |
X-Brykto-Timestamp |
When the call was made |
The signature is an HMAC-SHA256 of the timestamp, a full stop, and the raw request body, signed with your webhook secret.
The signed string is {timestamp}.{body}, not the body on its own.
Checking it
import hmac
import hashlib
def verify(body: bytes, signature_header: str, timestamp_header: str, secret: str) -> bool:
signed = timestamp_header.encode("utf-8") + b"." + body
expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature_header)
Two things are easy to get wrong here.
- Use the raw request body exactly as it arrived. If your framework parses the JSON and you rebuild it before checking, the signature will not match.
- The header value includes the
sha256=prefix. Compare against the whole value, or strip the prefix from both sides.
Your webhook secret
Find it under Developer Tools. Rotate it there if it may have been exposed, then update your server.
Ignoring repeats
Brykto retries a webhook up to three times, so the same event can reach you more than once.
Each order.paid event carries the transaction reference as tx_hash. Record the ones you have handled and ignore any that arrive twice, so a retry never ships the same order to your customer again.