Samva is in early access — self-serve signup is limited. Have a team invite? Sign up with that email. Contact us for access.

Verify webhooks

Verify the signature on Samva's outbound webhooks with the samva SDK's samva/webhooks export.

Verify webhooks

Samva signs every outbound webhook so you can confirm a request genuinely came from Samva before acting on it. The samva SDK's samva/webhooks export verifies that signature for you — it's edge-safe (WebCrypto), so the same code runs on Node, Bun, Cloudflare Workers, and Vercel Edge.

samva/webhooks only verifies signatures. Create endpoints and rotate signing secrets with the webhooks API or the SDK.

The signature

Each delivery is a POST with the signature in a header:

POST /webhooks/samva
X-Webhook-Signature: sha256=<hex>
X-Webhook-Event: message.delivered
X-Webhook-Id: <endpoint id>

{ "event": "message.delivered", "messageId": "msg_…", "timestamp": "…", "data": {  } }

The signature is HMAC-SHA256(secret, rawBody), hex-encoded. Verify the raw body — re-serializing it (for example, via JSON middleware) changes the bytes and breaks verification.

Verify a request

bun add samva

A Web Request (Next.js Route Handlers, Hono, TanStack Start, Remix, Workers):

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

export async function POST(req: Request) {
  try {
    const event = await verifyRequest(req, process.env.SAMVA_WEBHOOK_SECRET!);
    switch (event.event) {
      case "message.delivered":
        // mark delivered…
        break;
      case "message.bounced":
        // suppress the address…
        break;
    }
    return new Response(null, { status: 200 });
  } catch (err) {
    if (err instanceof WebhookVerificationError) {
      return new Response("invalid signature", { status: 400 });
    }
    throw err;
  }
}

A Node IncomingMessage — use the /node adapter and a raw-body parser, since JSON middleware re-serializes the body:

import { verifyNodeRequest } from "samva/webhooks/node";
// app.post("/webhooks/samva", express.raw({ type: "application/json" }), handler)

Tips

  • Return 2xx fast, then do any slow follow-up work asynchronously.
  • Be idempotent — webhooks are delivered at least once. Dedupe on event + messageId together; a single message emits several events (sent, delivered, read, …), so messageId alone would drop valid ones.

For the full list of events and for endpoint management, see the webhooks API reference.

On this page