verifyWebhookSignature validates that incoming webhook requests originated from ArcenPay. Always verify signatures before processing webhook payloads.
verifyWebhookSignature
import { verifyWebhookSignature } from "@arcenpay/node";
verifyWebhookSignature(
rawBody: string,
signature: string,
secret: string,
): boolean
| Param | Type | Description |
|---|---|---|
rawBody | string | The raw request body as a string (before JSON parsing). |
signature | string | The X-ArcenPay-Signature header value. Pass the raw header — the function strips the sha256= prefix automatically. |
secret | string | Your whsec_... signing secret. |
true if the signature matches, false otherwise.
Express example
import express from "express";
import { verifyWebhookSignature } from "@arcenpay/node";
const app = express();
app.post(
"/webhooks/arcenpay",
express.raw({ type: "application/json" }),
(req, res) => {
const rawBody = req.body.toString("utf-8");
const header = req.headers["x-arcenpay-signature"] as string ?? "";
if (!verifyWebhookSignature(rawBody, header, process.env.ARCENPAY_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(rawBody);
console.log(`Received ${event.type}`);
res.status(200).json({ received: true });
}
);
Next.js App Router example
import { NextRequest, NextResponse } from "next/server";
import { verifyWebhookSignature } from "@arcenpay/node";
export async function POST(request: NextRequest) {
const rawBody = await request.text();
const header = request.headers.get("x-arcenpay-signature") ?? "";
if (!verifyWebhookSignature(rawBody, header, process.env.ARCENPAY_WEBHOOK_SECRET!)) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
const event = JSON.parse(rawBody);
switch (event.type) {
case "subscription.minted":
await activateService(event.data.companyId);
break;
case "subscription.renewal_failed":
await suspendService(event.data.companyId);
break;
case "company.plan.cancelled":
await revokeAccess(event.data.companyId);
break;
case "company.plan.changed":
await updateEntitlements(event.data.companyId);
break;
case "invoice.paid":
await confirmPayment(event.data.invoiceId);
break;
}
return NextResponse.json({ received: true });
}
Next.js Pages Router example
import type { NextApiRequest, NextApiResponse } from "next";
import { verifyWebhookSignature } from "@arcenpay/node";
export const config = {
api: { bodyParser: false },
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") return res.status(405).end();
const rawBody = await getRawBody(req);
const header = (req.headers["x-arcenpay-signature"] as string) ?? "";
if (!verifyWebhookSignature(rawBody, header, process.env.ARCENPAY_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(rawBody);
await handleEvent(event);
res.status(200).json({ received: true });
}
async function getRawBody(req: NextApiRequest): Promise<string> {
return new Promise((resolve, reject) => {
let data = "";
req.on("data", (chunk) => { data += chunk; });
req.on("end", () => resolve(data));
req.on("error", reject);
});
}
Always read and verify the raw request body before JSON parsing. Parsing first and re-serializing can change the byte sequence and cause verification to fail.
Related
- Webhooks integration — event types, idempotency, and retry behavior
- Authentication — webhook signing secret management
- ArcenClient reference — all client methods

