Skip to content

Verify webhooks

Your webhook address is public, so anyone can send it a request that looks like Sotirea’s. Check every delivery’s signature before you act on it. The clients do it in one call, which checks the signature against your secret and refuses a delivery more than five minutes old, so a captured one cannot be replayed later.

func receive(w http.ResponseWriter, r *http.Request) {
// Read the body exactly as it arrived: the signature covers these bytes.
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
http.Error(w, "unreadable", http.StatusBadRequest)
return
}
secret := os.Getenv("SOTIREA_WEBHOOK_SECRET")
event, err := sotirea.VerifyWebhook(secret, r.Header, body)
if err != nil {
// Not from Sotirea, altered, or too old: never act on it.
http.Error(w, "not verified", http.StatusUnauthorized)
return
}
if event.Type == sotirea.EventDepositReceived {
d, err := event.Deposit()
if err != nil {
http.Error(w, "unreadable", http.StatusBadRequest)
return
}
if err := credit(d); err != nil {
// Anything but a 2xx and Sotirea tries again later.
http.Error(w, "try again", http.StatusServiceUnavailable)
return
}
}
w.WriteHeader(http.StatusNoContent)
}

The TypeScript receive takes a standard Request and answers a Response, so it works as a route handler in Next.js, in Hono or Bun, or behind Node’s own server.

  • Parsing before verifying. The signature covers the body’s exact bytes. A framework that parses JSON first and hands you an object has thrown them away: re-serialising it will not match. Read the raw body, verify it, then parse.
  • The wrong secret. Each webhook has its own secret, shown once when it was added. Test and Live webhooks are different webhooks with different secrets.
  • A slow clock. A server whose clock is more than five minutes out refuses every delivery. Keep it synchronised.
  • Answering slowly. Answer within 10 seconds or Sotirea counts the delivery as failed and sends it again. Record the event, answer 2xx, and do slow work afterwards.

Sotirea signs to the Standard Webhooks specification, so its library for your language verifies Sotirea’s webhooks with the same secret. To do it yourself: compute the HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<body>, keyed with the base64-decoded part of the secret after whsec_, base64-encode it, and compare it in constant time with each v1, signature in webhook-signature, which can hold more than one, space separated, while a secret is being replaced. See webhooks for the events and retries.