Webhooks

PayLater sends real-time notifications to your server via HTTP POST with a JSON body whenever a key transaction event occurs — so you don’t have to poll Check Payment Status.

Before you start

To receive webhooks you need:

  1. A public HTTPS endpoint that accepts POST requests.
  2. A webhook secret — request one from your PayLater account manager. It’s used to verify that each notification genuinely came from PayLater.
POST https://your-server.com/paylater/webhook  ·  your endpoint

The webhook request

Headers

HeaderValue
Content-Typeapplication/json

Body

{
  "merchantId": "<merchant_id>",
  "orderId": "<order_id>",
  "paylaterRef": "PL1746499849330726",
  "status": "<status>",
  "timestamp": "<timestamp>",
  "signature": "<signature>",
  "txHash": "<tx_hash>",
  "comments": "<comments>"
}
Payload fields
merchantIdString
Your unique merchant identifier.
orderIdString
The order identifier you supplied when creating the payment.
paylaterRefString
PayLater’s internal reference for the order.
statusString
Order status — one of success, failed, pending.
timestampLong
Epoch timestamp of the event.
txHashString
MD5 hash of the concatenated payload (see below).
signatureString
HMAC SHA-256 of txHash, keyed with your webhook secret.
commentsString
Optional comments / metadata.

Verifying a webhook

Always verify before trusting a webhook

Never act on a webhook until both checks below pass. An unverified request could come from anyone. Respond 200 only after verification succeeds.

Verification is two steps:

  1. Reconstruct txHash — concatenate merchantId + orderId + status + timestamp + comments, uppercase the result, and take its MD5 hash. It must equal the txHash field.
  2. Validate signature — compute the HMAC SHA-256 of txHash using your merchantWebhookSecret. It must equal the signature field.
const crypto = require('crypto');

function verifyWebhook(req) {
  const { merchantId, orderId, status, timestamp, txHash, signature, comments } = req.body;

  // 1. Reconstruct and compare txHash
  const data = `${merchantId}${orderId}${status}${timestamp}${comments}`.toUpperCase();
  const computedTxHash = crypto.createHash('md5').update(data).digest('hex');
  if (computedTxHash !== txHash) return false;

  // 2. Validate the signature
  const computedSignature = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(txHash)
    .digest('hex');

  return computedSignature === signature;
}
<?php
$MERCHANT_SECRET = getenv('WEBHOOK_SECRET');
$request = json_decode(file_get_contents("php://input"), true);

// 1. Reconstruct txHash
$dataString = strtoupper(
    $request['merchantId'] . $request['orderId'] . $request['status'] .
    $request['timestamp'] . $request['comments']
);
$computedTxHash = md5($dataString);

// 2. Validate signature
$computedSignature = hash_hmac("sha256", $computedTxHash, $MERCHANT_SECRET);

if ($computedTxHash === $request['txHash'] && $computedSignature === $request['signature']) {
    http_response_code(200);
    echo json_encode(["message" => "Webhook received successfully"]);
} else {
    http_response_code(403);
    echo json_encode(["message" => "Invalid signature"]);
}
Respond quickly, process asynchronously

Return 200 OK as soon as verification passes, then do heavy work (order fulfilment, emails) on a background queue. PayLater may retry if it doesn’t receive a timely 2xx.