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.
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 event = await verifyRequest(
request,
process.env.SAMVA_WEBHOOK_SECRET!,
);
await handleIdempotently(request.headers.get("webhook-id")!, 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 webhook-id under a unique constraint
before applying side effects.
Retries use the same webhook-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 logs are retained for 90 days. Samva disables an endpoint after five continuous days without a successful delivery.