# 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 the notifyPayload 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, select Production 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\"}
}