# API Notifications
Charges take 3-10 minutes to settle once the customer makes the payment. Once a charge is complete, you might require your internal systems and databases to be updated. You may also wish to take appropriate actions relevant to your business. For instance, an ecommerce vendor would want to dispatch the order and update inventory.
API Notifications can be used to facilitate communication between your system and XanPay. They notify your system and execute appropriate logic. There are two types of notification systems available (1) Webhooks (2) Callbacks. Both receive the same information in the request payload. The format of the requests varies with the version number. We recommend using Webhooks, but if you have a special case which requires the use of callbacks, please reach out to us on the dashboard (opens new window).
# Charge data
If you want your endpoint to receive additional data, such as the your internal order identifier, set the notifyPayload
parameter when you are creating the charge. This argument accepts strings. In case you wish to send an object e.g {"orderId": "251"}, you can convert it into a string by encoding into base64, e.g. "eyJvcmRlcklkIjogIjI1MSJ9".
If you are using a checkout link, you can send an object with the required data in the
notifyPayload
parameter.If you are creating a charge using the REST API, and making the following API call
POST /charges
request, you can send an object with the required data in thenotifyPayload
parameter.
# Authentication
Your endpoint should use a certificate for authenticating requests. You need to generate a certificate on the and then each time you receive a callback request, you must authenticate the request by verifying the certificate sent.
# Generate certificate
- Navigate to API Keys (opens new window) on the dashboard sidebar.
- Click
Create certificate
. - Select the appropriate certificate depending on your purpose. If you are creating the certificate for testing, select
Sandbox certificate
. If you are ready for primetime, selectProduction certificate
.
Endpoints should authenticate the request by verifying the certificate sent by verifying against the signature. However, how the signature must be retrieved and verified depends on the API notification version of the notifications you receive. Please read through to the appropriate section and update appropriately.
Following is a sample public key.
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2JNXtYjaKmHY4XuMii6D
PI86/X1r3iBg7ujyG3bdpUuUpKkrYkEvVo1aUTVPWTQ8XipAukldEZhhdJZkCbot
ZGN5G/oZNhTM6kg83q+iHTahYjZhI6sFUIjpqHNLsTkCNsAPkxVcX59rlzSySwPg
Yi3lPMj5l91Lw7eS59Std1P9XuDoWMjo8eh11z0oaWYXwiaYRJtWU+8jYe3M5iwJ
ljgxpLAwxp+YIsRAbNxr5YkJwGb774Ux4gBH6DsQY6HTmdlOLYsjcj4qdvzVTBhy
91gvsn4Qw1lz32L74pVEuwCP0MaMJeqlPkPTD3r51jLsoUMDvKdLx7uVNUHG5lqD
DQIDAQAB
-----END PUBLIC KEY-----
# Notifications
Notifications are triggered when certain events
take place. The endpoint will also receive a data
parameter with an object relevant to the event. For instance, in case a refund is triggered, we will send a refund object in the data
parameter in the request.
The format of the request body we send to your endpoint includes the following fields.
Attributes
Parameter | Description |
---|---|
createdAt | Time when the notification was triggered in UTC |
version | 0.9 notification format version |
webhookId | ID of the registered webhook, if any |
event | Event type documented here |
data | Object (e.g. charge) relevant to the event |
# Events
Event | Description | Data |
---|---|---|
charge_completed | Charge status is updated to completed | Charge object |
charge_created | Charge is created with status pending | Charge object |
charge_updated | Charge status is updated to pending, expired, cancelled | Charge object |
refund_initiated | Refund is initiated | Refund object |
refund_processing | Refund is being processed | Refund object |
refund_completed | Refund is completed | Refund object |
# Example notification: charge_completion event
{
"timestamp": "2021-12-14T04:29:57.078Z",
"version": 0.9,
"webhookId": "605015227b87ad0011c63550",
"event": "charge_completed",
"data": {
"id": "506c80f6a5b44800116d7c16",
"merchantId": "605015227b87ad0011c63549",
"customer": {
"id": "61ee0e93419cd7001164bb5f",
"email": "customer_example@gmail.com",
"phone": "+6512345678901"
},
"customerCurrency": "SGD",
"customerAmount": 100,
"merchantAmount": 100,
"merchantCurrency": "SGD",
"orders": [
{
"id": "unique-phone-id",
"name": "Phone",
"quantity": 1,
"amount": 100,
"currency": "SGD"
}
],
"method": "paynow",
"status": "completed",
"notifyPayload": "eyJvcmRlcklkIjogIjI1MSJ9",
"createdAt": "2021-12-14T03:28:57.796Z",
"updatedAt": "2021-12-14T04:29:57.078Z"
}
}
# Example notification: refund_completion event
{
"timestamp": "2021-12-14T04:29:57.078Z",
"version": 0.9,
"webhookId": "605015227b87ad0011c63550",
"event": "refund_completed",
"data": {
"chargeId": "609c80f6a5b44800116d7c16",
"customer": {
"id": "61ee0e93419cd7001164bb5f",
"email": "customer_example@gmail.com",
"phone": "+6512345678901"
},
"method": "paynow",
"customerAmount": 10,
"customerCurrency": "SGD",
"status": "completed",
"createdAt": "2021-12-14T03:28:57.796Z",
"updatedAt": "2021-12-14T04:29:57.078Z"
}
}
# Authentication 0.9
To ascertain that a request is originating on a XanPay server, you can retrieve the x-signature
from the header and verify that it matches up with the body of the request.
# Verification example in PHP
private function checkSignature($header, $body, $publicKey)
{
$signature = $header['x-signature'];
return (bool)openssl_verify(
$body,
base64_decode($signature),
$publicKey,
OPENSSL_ALGO_SHA256
);
}
# Verification example in JavaScript
const crypto = require("crypto");
const verifySignature = (headers, body, publicKey) => {
const { "x-signature": signature } = headers;
const publicKeyBuf = Buffer.from(publicKey, "ascii");
const signatureBuf = Buffer.from(signature, "base64");
const verifier = crypto.createVerify("RSA-SHA256");
verifier.update(body, "ascii");
return verifier.verify(publicKeyBuf, signatureBuf);
};
# Notifications 0.7
Deprecated
This notification format is supported but will be deprecated.
Please migrate to the latest notification format.
The format of the request body we send to your endpoint is as follows. Your endpoint should process this format and retrieve relevant information.
Parameter | Description |
---|---|
timestamp | Time when the webhook was triggered in number of milliseconds since January 1, 1970, 00:00:00 |
version | 0.7 |
signature | Certificate to be checked to ensure that the notification is coming from XanPay servers |
reason | string, "charge_status_updated" |
payload | Information relevant to the notification |
# Payload object
Parameter | Description |
---|---|
chargeId | ID of the charge |
merchantId | ID of the merchant account |
webhookId | ID of the registered webhook |
customerAmount | Amount charged in customer's currency |
customerCurrency | Customer's currency, depends on the method |
merchantAmount | Amount charged in merchant's currency |
merchantCurrency | Merchant's currency |
paymentMethod | Customer's payment method |
orders | Orders object |
notifyPayload | Charge data set during charge creation |
status | Current charge status |
customer | Customer object including email and phone |
** Example **
{
"timestamp": 1618226757.504,
"version": 0.7,
"reason": "charge_status_updated",
"payload": {
"chargeId": "609c80f6a5b44800116d7c16",
"merchantId": "605015227b87ad0011c63549",
"webhookId": "605015227b87ad0011c63550",
"customerCurrency": "SGD",
"customerAmount": 100,
"merchantAmount": 100,
"merchantCurrency": "SGD",
"paymentMethod": "paynow",
"orders": [
{
"id":"unique-phone-id",
"name":"Phone",
"quantity":1,
"amount":100,
"currency":"SGD"
}
],
"notifyPayload": "eyJvcmRlcklkIjogIjI1MSJ9",
"status": "completed",
"subscriptionId": "61ee3835419cd7001164c064",
"customer": {
"id": "61ee0e93419cd7001164bb5f",
"email": "customer_example@gmail.com",
"phone": "+6512345678901"
}
}
}
# Authentication 0.7
To ascertain that a request is originating on a XanPay server, you can use the signature parameter in the request "payload". The signature
parameter sent to endpoints is a base64 encoded RSA signature.
"signature": "eDHwjHB0TJql2O98E01bUcNZ6aLjH4ugZIw2eeT3a+7xtA0VYTQE6VpnKvkjQG0QlBDGNelfztI0FJ+8hIHsxNW2gK0o+LoHlrBYyIFNbxIbU0DmWkxWyKJuoV9kOHADc+Xkl7DDlBYYgPB0WyJXHp9w9dSyW5LylKJTaf4GoQ83kKFxCYmz769PH1Vfc+d9eujkqGLLy4tAApZyLseGHcveMcKWcONltRXZChqtzCee5JKjI+ZkfBfeCDPRIM+hTLSUSgU13RQbv5oqAReyS9FXiCrNC2Xg6sgRsQ+btpx5HH5Z9e2WSLjTRrn+5inAkPqnteegs/s2xIeltV2jyA"
# Verification example in PHP
private function checkSignature($body, $publicKey)
{
$payload = $body['payload'];
$signature = $body['signature'];
return (bool)openssl_verify(
$payload,
base64_decode($signature),
$publicKey,
OPENSSL_ALGO_SHA256
);
}
# Verification example in JavaScript
const crypto = require("crypto");
const verifySignature = (body, publicKey) => {
const { signature, payload } = body;
const publicKeyBuf = Buffer.from(publicKey, "ascii");
const signatureBuf = Buffer.from(signature, "base64");
const verifier = crypto.createVerify("RSA-SHA256");
verifier.update(payload, "ascii");
return verifier.verify(publicKeyBuf, signatureBuf);
};
# Notifications 0.5
Deprecated
This notification format is no longer supported.
Please migrate to the latest notification format.
The format of the request body we send to your endpoint is as follows. Your endpoint should process this format and retrieve relevant information.
Parameter | Description |
---|---|
timestamp | Time when the webhook was triggered in number of milliseconds since January 1, 1970, 00:00:00 |
version | 0.5 |
signature | Certificate to be checked to ensure that the notification is coming from XanPay servers |
reason | string, "charge_status_updated" |
payload | Stringified JSON with information relevant to the notification |
# Payload object
Stringified JavaScript object with information relevant to the notification
Parameter | Description |
---|---|
chargeId | ID of the charge |
merchantId | ID of the merchant account |
webhookId | ID of the registered webhook |
customerAmount | Amount charged in customer's currency |
customerCurrency | Customer's currency, depends on the method |
merchantAmount | Amount charged in merchant's currency |
merchantCurrency | Merchant's currency |
paymentMethod | Customer's payment method |
orders | Orders object |
notifyPayload | Charge data set during charge creation |
status | Current charge status |
customer | Customer ID |
{
"timestamp": 1618226757.504,
"version": 0.5,
"reason": "charge_status_updated",
"payload": {\"chargeId\":\"609c80f6a5b44800116d7c16\",\"merchantId\":\"605015227b87ad0011c63549\",\"webhookId\":\"605015227b87ad0011c63550\",\"customerCurrency\":\"SGD\",\"customerAmount\":100,\"merchantAmount\":100,\"merchantCurrency\":\"SGD\",\"paymentMethod\":\"paynow\",\"orders\":[{\"id\":\"unique-phone-id\",\"name\":\"Phone\",\"quantity\":1,\"amount\":100,\"currency\":\"SGD\"}],\"notifyPayload\":\"eyJvcmRlcklkIjogIjI1MSJ9\",\"status\":\"completed\",\"subscriptionId\":\"61ee3835419cd7001164c064\",\"customer\":\"61ee0e93419cd7001164bb5f\"}
}
← Webhooks