Receive webhooks

Register an endpoint and verify Standard Webhooks signatures.

Samva customer webhooks deliver message events to your application. They are separate from provider ingress, which is how Samva receives upstream delivery events.

1. Register an endpoint

Send a public HTTPS URL and the event types and channels it should receive:

curl -X POST https://api.samva.dev/v1/webhooks \
  -H "X-API-Key: samva_sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order events",
    "url": "https://your-app.com/webhooks/samva",
    "eventTypes": ["message.delivered", "message.failed"],
    "channels": ["email"]
  }'
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const result = await samva.webhooks.create({
  name: "Order events",
  url: "https://your-app.com/webhooks/samva",
  eventTypes: ["message.delivered", "message.failed"],
  channels: ["email"],
});

const signingSecret = result.secret;

Store the returned secret immediately; Samva does not show it again. Secret rotation returns a new secret and keeps the previous one valid for 24 hours.

Custom endpoint headers are allowed, but Samva's reserved delivery headers cannot be overridden. Secret-looking custom header values are redacted in API responses and delivery logs. Endpoint URLs cannot contain credentials and must resolve entirely to publicly routable addresses.

2. Verify before parsing

Every request includes webhook-id, webhook-timestamp, and webhook-signature. Verify all three against the exact raw body before parsing JSON. The timestamp must be within five minutes.

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

export async function POST(request: Request) {
  try {
    const verified = await verifyRequest(
      request,
      process.env.SAMVA_WEBHOOK_SECRET!,
    );
    await persistAndHandleOnce(verified.id, verified.event);
    return new Response(null, { status: 204 });
  } catch (error) {
    if (error instanceof WebhookVerificationError) {
      return new Response("Invalid webhook", { status: 401 });
    }
    throw error;
  }
}

Use samva/webhooks/effect in Effect applications. Both exports are runtime-neutral; there is no /node adapter.

3. Handle delivery correctly

Samva treats any 2xx as accepted and gives each POST 30 seconds. Delivery is at least once and unordered, so persist verified.id under a unique constraint before applying side effects. verified.id matches the webhook-id header.

Retries use the same verified.id with a fresh timestamp and signature. The retry delays are approximately 5s, 5m, 30m, 2h, 5h, 10h, and 10h, with jitter. A valid Retry-After header can move the next attempt later.

Test requests are queued through this same lifecycle. Manual redelivery creates one immediate attempt and does not start a new automatic retry series. Delivery does not follow redirects, so the registered URL must answer the request directly. Logs retain attempt number, status, latency, error code, and a small allowlist of response headers for 90 days. Response bodies are never stored. Samva disables an endpoint after five continuous days without a successful delivery.

Next steps

On this page