Webhooks
Instead of polling a session to completion, subscribe to webhooks and let Sovera call you when something happens. Sovera signs every webhook so you can prove it came from Sovera and was not tampered with.
Thin payloads
Sovera webhooks are deliberately thin. The payload tells you what happened and which resource changed, not the full result. On receipt, call the API (for example GET /v1/sessions/{id}) to fetch the current, authoritative state. Thin payloads keep sensitive data out of your logs and mean a replayed webhook can never carry stale results.
Example webhook body
{
"id": "evt_2b81f0",
"type": "session.completed",
"createdAt": "2026-07-04T18:02:44Z",
"data": {
"sessionId": "ses_8f2c1a7b"
}
}
The signature header
Every webhook carries a Sovera-Signature header with a timestamp and an HMAC-SHA256 over timestamp.rawBody, keyed with your webhook signing secret.
Sovera-Signature header
Sovera-Signature: t=1751652164,v1=3f9a2c8e7b1d0a6f4c2e9b8a7d6c5f4e3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d
Verify it by recomputing HMAC-SHA256(secret, "{t}.{rawBody}") and comparing it to v1 in constant time. Reject the request if the signature does not match, or if t is too far from the current time (guarding against replay).
Verify a webhook
Use the raw request body exactly as received. Do not parse and re-serialize the JSON first, or the bytes will differ and the signature will not match.
import crypto from 'node:crypto'
function verifySoveraWebhook(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(
header.split(',').map((kv) => kv.split('=')),
)
const timestamp = Number(parts.t)
const signature = parts.v1
// Reject stale timestamps to prevent replay
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSec) return false
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(signature)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
Respond with a 2xx quickly and do the real work asynchronously. Sovera treats a non-2xx as a failed delivery and may retry, so make your handler idempotent on the event id.