CP-Pay Webhooks
Receive payment-success and refund events on your own backend.
The events currently sent are payment.succeeded and payment.refunded; if you omit events, these two are the default. Webhooks registered with the legacy name payment.completed also receive payment.succeeded. Refund events go only to the webhook of the same environment (test/live) as the original payment.
Registration
Register your webhook URL from the cp-platform.com/developers console. ngrok URLs work fine for local development.
https://pay.cp-platform.com/developers/apps/{app_id}/webhook{
"url": "https://your-app.com/webhooks/cp-pay",
"events": ["payment.succeeded", "payment.refunded"]
}Store the secret from the response (used for signature verification). To rotate it, call POST .../webhook/regenerate-secret.
HMAC signature verification
CP-Pay sends an X-CP-Webhook-Signature: t=<unix seconds>,v1=<hex> header with every delivery. v1 is the HMAC-SHA256 of `${t}.${rawBody}` keyed with your secret. The event name is in the X-CP-Webhook-Event header and the body event field.
import crypto from "node:crypto";
export async function POST(req: Request) {
const rawBody = await req.text();
const header = req.headers.get("x-cp-webhook-signature") ?? "";
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const signature = parts.v1 ?? "";
const expected = crypto
.createHmac("sha256", process.env.CP_WEBHOOK_SECRET!)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
if (signature.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(rawBody);
switch (event.event) {
case "payment.succeeded":
// handle payment success
break;
case "payment.refunded":
// handle refund (event.data.original_payment_id = original payment id)
break;
}
return Response.json({ ok: true });
}Retry policy
- Non-2xx response → up to 5 retries with exponential backoff (1m / 5m / 30m / 2h / 12h)
- After 5 failures the event moves to the Dead Letter Queue
- Delivery history is available in the console's Delivery Log
https://pay.cp-platform.com/developers/apps/{app_id}/webhook/deliveries