PayZuDocs

Notifications sent to your system when the charge status changes.

Instead of your system polling "has it been paid yet?", PayZu calls you when something happens: a charge status change, a fraud analysis update, a chargeback or a new recurrence cycle.

How to configure

Provide the postbackUrl when creating the charge (POST /charges). Whenever an event occurs, PayZu sends a POST request in JSON to that URL.

Events

Event typesDescription
charge.updatePayment status change
antifraud.updateFraud analysis completed, reflected in the charge's status and reasonCode
chargebackChargeback notification
recurrence.cycleNew recurrence cycle charged

Payload structure

ParametersDescriptionType
eventEvent that triggered the webhookSee the events table
dataUpdated charge dataSame value returned by Get charge
{
  "event": "charge.update",
  "data": {}
}

The data object has exactly the same format as the Get charge response.

Request headers

Every POST arrives with the following headers:

HeaderDescription
Content-TypeAlways application/json
X-Webhook-SignatureHMAC SHA-256 signature of the payload, in hexadecimal (64 characters)
X-Webhook-TimestampTime of sending, in milliseconds since the Unix epoch
X-Webhook-NonceUnique identifier of the request (32 hexadecimal characters)

Retries

The first delivery happens as soon as the event occurs. The delivery is only considered successful if your URL responds with an HTTP 2xx status within 5 seconds: any other status, or a slower response, counts as a failure.

After a failure, the webhook makes up to 5 retries. After each failure, the time until the next attempt increases: the retries are made, respectively, after 1 minute, 10 minutes, 1 hour, 6 hours and 24 hours. After that, the attempts stop.

Respond to the webhook quickly (a simple 200 is enough) and process the payload asynchronously, so you do not exceed the 5-second limit. Since a timeout can trigger a redelivery of an event you already processed, consumption must be idempotent: use the charge id combined with the status transition as your deduplication key. Do not use X-Webhook-Nonce for this, it identifies the HTTP request and changes on every redelivery.

HMAC verification

Every webhook is signed with your webhook secret, provided by PayZu along with your API credentials. Your API must validate the signature before processing the payload:

Extract the x-webhook-timestamp, x-webhook-nonce and x-webhook-signature headers.

Concatenate the timestamp, nonce and payload values, separated by ., forming the verification base string: timestamp.nonce.payload.

Generate an HMAC signature with the SHA-256 algorithm from that string, using your webhook secret.

Compare the generated signature with the value of the x-webhook-signature header. If they do not match, reject the webhook.

Example in Node.js, using crypto.timingSafeEqual to compare the signatures in constant time:

const crypto = require("node:crypto");

function verifyWebhookSignature(request, webhookSecret) {
  const timestamp = request.headers["x-webhook-timestamp"];
  const nonce = request.headers["x-webhook-nonce"];
  const signature = request.headers["x-webhook-signature"];

  if (typeof signature !== "string" || !/^[0-9a-f]{64}$/i.test(signature)) {
    return false;
  }

  const baseString = `${timestamp}.${nonce}.${request.rawBody}`;
  const expectedSignature = crypto
    .createHmac("sha256", webhookSecret)
    .update(baseString)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature, "hex"),
    Buffer.from(signature, "hex"),
  );
}

Compute the HMAC over the raw request body, exactly as received, before any JSON parsing.

Nonce verification (optional)

The value of the x-webhook-nonce header acts as a unique, temporary identifier for each request. After extracting it, check whether that nonce has been recorded before:

  • If the value has already been used, reject the request to mitigate replay attacks.
  • If the nonce is new, store it as used, ensuring it cannot be reused in future calls.

Timestamp verification (optional)

The value of the x-webhook-timestamp header is the time of sending in milliseconds since the Unix epoch. Compare it with the current time: if the difference is greater than 5 minutes, reject the request. This validation discards expired webhooks, preventing the processing of old or potentially malicious messages.

On this page