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:
- A public HTTPS endpoint that accepts
POSTrequests. - 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
| Header | Value |
|---|---|
Content-Type | application/json |
Body
{
"merchantId": "<merchant_id>",
"orderId": "<order_id>",
"paylaterRef": "PL1746499849330726",
"status": "<status>",
"timestamp": "<timestamp>",
"signature": "<signature>",
"txHash": "<tx_hash>",
"comments": "<comments>"
}
Payload fields
merchantIdStringYour unique merchant identifier.
orderIdStringThe order identifier you supplied when creating the payment.
paylaterRefStringPayLater’s internal reference for the order.
statusStringOrder status — one of
success, failed, pending.timestampLongEpoch timestamp of the event.
txHashStringMD5 hash of the concatenated payload (see below).
signatureStringHMAC SHA-256 of
txHash, keyed with your webhook secret.commentsStringOptional 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:
- Reconstruct
txHash— concatenatemerchantId + orderId + status + timestamp + comments, uppercase the result, and take its MD5 hash. It must equal thetxHashfield. - Validate
signature— compute the HMAC SHA-256 oftxHashusing yourmerchantWebhookSecret. It must equal thesignaturefield.
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.