Webhooks
One signature-verified handler for every provider's events. payments.webhooks.handle() verifies and normalizes any provider's payload into a single event schema.
A webhook is an HTTP request a provider sends to your server when something
happens — a redirect payment completes, a dispute opens, a subscription renews.
Every provider signs and shapes these differently. payments.webhooks.handle()
verifies the signature and collapses them into one NormalizedEvent, so you
write a single handler.
The handler
import { payments } from "@/lib/payments";
export async function POST(req: Request) {
const event = await payments.webhooks.handle({
body: await req.text(), // RAW body — see the warning below
headers: Object.fromEntries(req.headers),
});
switch (event.type) {
case "charge.succeeded":
await fulfillOrder(event.data.metadata.orderId);
break;
case "refund.succeeded":
await markRefunded(event.data.id);
break;
case "subscription.canceled":
await revokeAccess(event.data.customer.email);
break;
}
return new Response("ok"); // 2xx tells the provider you received it
}You must pass the RAW body
Signatures are computed over the exact bytes the provider sent. Pass the raw
string/Buffer (await req.text(), or req.rawBody on Node frameworks) —
never JSON.parsed-then-re-serialized data, or verification will fail every
time. In Next.js route handlers, req.text() gives you the raw body.
The normalized event
typestringThe normalized event name, e.g. charge.succeeded, charge.failed,
refund.succeeded, subscription.updated, subscription.canceled.
dataChargeResult | RefundResult | SubscriptionResultThe normalized object the event is about (same shapes as the API returns).
providerstringWhich provider sent it.
rawunknownThe original, untouched provider payload.
Configuring the signing secret
Set each provider's webhook signing secret via webhookSecret (or its
RADON_<PROVIDER>_WEBHOOK_SECRET env var). See each provider's page for exactly
which secret to use.
const payments = new RadonPayments({
providers: {
stripe: { webhookSecret: process.env.RADON_STRIPE_WEBHOOK_SECRET },
},
});With multiple providers, name the sender
Radon infers the provider from the signature only when exactly one is
configured. With several, pass { provider } so it knows which verifier to use:
await payments.webhooks.handle({ body, headers }, { provider: "stripe" });Otherwise it throws InvalidConfigError.
Provider-specific behavior
Return the right status
Returning a 2xx response tells the provider the event was received. If your
handler throws, respond with a 4xx/5xx so the provider retries. Radon throws
WebhookSignatureError when verification fails — treat
that as a 400.