Skip to main content
Your webhook URL is reachable by anyone who learns it. Without a signature check, a stranger can POST payment.paid at it and have you ship goods nobody paid for. Verify every request before your handler does anything. Creptapay signs each delivery with your secret API key (sk_...) for that environment — the same key you use for server-to-server calls. Live deliveries are signed with your live key, sandbox with your test key.
Keys issued before signed webhooks shipped cannot sign. If your endpoint is receiving nothing at all and the logs show a signing error, regenerate your API keys in Developer → API keys, then update the key on your server.

The signature

Every delivery carries one header:
t is the Unix timestamp in milliseconds and v1 is a hex HMAC-SHA256. To verify:
  1. Read t and v1 out of the header.
  2. Take the raw request body — the exact bytes we sent, before any JSON parsing.
  3. Compute HMAC-SHA256(secret_key, t + "." + raw_body) as lowercase hex.
  4. Compare it with v1 using a constant-time comparison.
  5. Reject anything where t is more than a few minutes old.
Step 2 is the one that catches people out. Most frameworks parse JSON before your handler runs, and re-serializing the parsed object will not reproduce our bytes — key order, unicode escaping and number formatting all differ between languages. You must capture the raw body.
Step 5 stops a replay: without it, anyone who captures one valid delivery can resend it to you forever.

Testing it

Send yourself a delivery from Developer → Logs — pick any entry and resend it. The log records the status your endpoint returned, so a 401 there means your verification is rejecting a genuine Creptapay request. The most common cause is a framework that parsed the body before you could keep the raw bytes. If the signature never matches, log the raw body’s length and first characters and compare them with the payload shown in the log.