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.

PUThttps://pay.cp-platform.com/developers/apps/{app_id}/webhook
Authentication
User token
Request body
{
  "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.

your-app/webhooks/cp-pay.ts
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
GEThttps://pay.cp-platform.com/developers/apps/{app_id}/webhook/deliveries
Authentication
User token