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)
}

Was this page helpful?