Verify webhooks

Verify Samva's Standard Webhooks signatures with the samva SDK.

Samva signs every outbound webhook using the Standard Webhooks convention. The samva SDK provides two first-class verifier exports:

  • samva/webhooks for Promise-based applications.
  • samva/webhooks/effect for Effect applications.

Both use WebCrypto and run on Node, Bun, Cloudflare Workers, and other edge runtimes. There is no runtime-specific /node adapter.

Create endpoints and rotate signing secrets with the webhooks API or the SDK. The signing secret is shown only when it is created or rotated.

Signed request

Each delivery is a POST with these Standard Webhooks headers:

POST /webhooks/samva
webhook-id: evt_01…
webhook-timestamp: 1786796531
webhook-signature: v1,base64-signature
content-type: application/json

{"type":"message.delivered","timestamp":"2026-01-15T09:42:11.204Z","data":{"messageId":"msg_01…","status":"delivered"}}

Verification covers the webhook-id, webhook-timestamp, and exact raw request body. Read the body before JSON middleware changes it. The verifier rejects timestamps outside the five-minute tolerance, which prevents replay of a previously captured request.

Verify a request

bun add samva
import { verifyRequest, WebhookVerificationError } from "samva/webhooks";

export async function POST(request: Request) {
  try {
    const event = await verifyRequest(
      request,
      process.env.SAMVA_WEBHOOK_SECRET!,
    );

    // Persist webhook-id under a unique constraint before processing.
    await handleEvent(event);
    return new Response(null, { status: 204 });
  } catch (error) {
    if (error instanceof WebhookVerificationError) {
      return new Response("invalid webhook", { status: 401 });
    }
    throw error;
  }
}

Effect applications can use the same contract without leaving Effect control flow:

import { verifyRequest } from "samva/webhooks/effect";

Delivery behavior

  • Return 2xx quickly. Samva gives each POST 30 seconds.
  • Delivery is at least once and unordered. Dedupe on webhook-id, not a message id or event type.
  • An automatic retry keeps the same webhook-id but uses a fresh timestamp and signature.
  • Automatic retries are scheduled after approximately 5s, 5m, 30m, 2h, 5h, 10h, and 10h, with jitter. A valid Retry-After response can delay the next attempt.
  • Rotating a secret keeps the previous secret valid for 24 hours.
  • Test deliveries are queued through the normal delivery lifecycle. Manual redelivery is a one-shot attempt and does not start another retry series.
  • Delivery logs are retained for 90 days. An endpoint is disabled after five continuous days without a successful delivery.

For endpoint management and event payloads, see the webhooks API reference and event catalog.

On this page