Instant Payment Notifications

Instant Payment notifications enable you to setup a notification system that provides updates on transaction requests made through the Xente API.

Why Instant Payment Notifications

All payment-related transactions (and some non-payment-related) requests on Xente are processed asynchronously. This is because most transactions require user approval and may take time to complete. Below is how we process transactions:

  1. You POST a transaction to Xente.

  2. We queue it up.

  3. We process the transaction - this might involve user approval.

  4. We call back your webhook to notify you of the final status of the transaction.

  5. You respond with an HTTP OK that you have received the notification and we commit the transaction as complete.

As a result, it is necessary to set up an Instant Transaction Notification webhook on your end in order to receive updates on the final status of a transaction

Handling Instant Payment Notification (IPN)

When it comes to handling Instant Payment Notification (IPN), there are a few key steps for you to follow:

  1. Set up an IPN URL: Create an endpoint on your server to receive IPN messages. This URL should be capable of handling incoming POST requests.

  2. JSON parsing: Ensure that your IPN URL can parse JSON data. This means extracting relevant information from the incoming request payload.

  3. Response status: If the JSON parsing is successful, make sure your IPN URL responds with a 200 OK status code. This lets the sender know that the notification was received and processed correctly.

  4. Transaction completion: Remember that a transaction is only marked as complete when your system receives a successful response from the IPN URL.

Below is some sample code to help you set up your Instant Payment Notification

                                    

curl -X POST \ curl -X POST \ 'your-url?transactionId={string}&subscriptionId={string}&accountId={string}&requestId={string}&Amount={double}' \ -H 'Content-Type: application/json' \ -d '{ "AccountId": "string", "SubscriptionId": "string", "TransactionId": "string", "BatchId": "string", "RequestId": "string", "StatusMessage": "string", "StatusCode": 0, "CorrelationId": "string", "CreatedOn": "YYYY-MM-DD HH:mm:ss", "Amount": double, "CurrencyCode": "string" }'

                                    

import requests import json url = "your-url" PaymentRequest = { "AccountId": "string", "SubscriptionId": "string", "TransactionId": "string", "BatchId": "string", "RequestId": "string", "StatusMessage": "string", "StatusCode": 0, # 0 for success, 1 for processing, 2 for error, etc. "CorrelationId": "string", "CreatedOn": "2022-01-01 12:00:00", # replace with the actual date and time "Amount": 0.0, # replace with the actual amount "CurrencyCode": "string" } # getting transactionId, subscriptionId, accountId, and requestId from the URL transactionId = input("Enter Transaction ID: ") subscriptionId = input("Enter Subscription ID: ") accountId = input("Enter Account ID: ") requestId = input("Enter Request ID: ") # adding the IDs to the URL url += f"?transactionId={transactionId}&subscriptionId={subscriptionId}&accountId={accountId}&requestId={requestId}" # converting the payload to JSON payload = json.dumps(payload) # sending the POST request response = requests.post(url, data=payload) # printing the response print(response.text)

                                    

package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", handlePost) http.ListenAndServe(":8080", nil) } func handlePost(res http.ResponseWriter, req *http.Request) { if req.Method == "POST" { transactionId := req.URL.Query().Get("transactionId") subscriptionId := req.URL.Query().Get("subscriptionId") accountId := req.URL.Query().Get("accountId") requestId := req.URL.Query().Get("requestId") amount := req.URL.Query().Get("Amount") // assuming request body is in JSON format var requestBody struct { AccountId string `json:"AccountId"` SubscriptionId string `json:"SubscriptionId"` TransactionId string `json:"TransactionId"` BatchId string `json:"BatchId"` // assuming this is a required field in the request body RequestId string `json:"RequestId"` StatusMessage string `json:"StatusMessage"` StatusCode int `json:"StatusCode"` CorrelationId string `json:"CorrelationId"` CreatedOn string `json:"CreatedOn"` Amount float64 `json:"Amount"` CurrencyCode string `json:"CurrencyCode"` } requestBody.StatusCode = 1 // setting default value for StatusCode // decoding request body and handling any errors err := json.NewDecoder(req.Body).Decode(&requestBody) if err != nil { http.Error(res, err.Error(), http.StatusBadRequest) return } // do processing with the received data res.WriteHeader(http.StatusOK) fmt.Fprintf(res, "POST request handled successfully") } else { http.Error(res, "Method not allowed", http.StatusMethodNotAllowed) } }

                                    

import org.springframework.web.bind.annotation.*; @RestController public class MyController { @PostMapping("/{your-url}") public void handlePostRequest( @PathVariable("your-url") String yourUrl, @RequestParam("transactionId") String transactionId, @RequestParam("subscriptionId") String subscriptionId, @RequestParam("accountId") String accountId, @RequestParam("requestId") String requestId, @RequestParam("Amount") double amount, @RequestBody RequestBody requestBody ) { // Handle the request String accountIdFromBody = requestBody.getAccountId(); String subscriptionIdFromBody = requestBody.getSubscriptionId(); String transactionIdFromBody = requestBody.getTransactionId(); String batchId = requestBody.getBatchId(); String statusMessage = requestBody.getStatusMessage(); int statusCode = requestBody.getStatusCode(); String correlationId = requestBody.getCorrelationId(); String createdOn = requestBody.getCreatedOn(); String currencyCode = requestBody.getCurrencyCode(); // Do something with the extracted parameters // ... } } class PaymentRequest{ private String AccountId; private String SubscriptionId; private String TransactionId; private String BatchId; private String RequestId; private String StatusMessage; private int StatusCode; private String CorrelationId; private String CreatedOn; private double Amount; private String CurrencyCode; // Getter and setter methods for all the fields }

                                    

using System.Web.Http; public class MyController : ApiController { [HttpPost] public IHttpActionResult MyEndpoint(string transactionId, string subscriptionId, string accountId, string requestId, double amount, [FromBody] MyPayload payload) { // Do something with the URL parameters // e.g. Save them to a database // Do something with the request body payload // e.g. Save it to a database return Ok(); } } public class MyPayload { public string AccountId { get; set; } public string SubscriptionId { get; set; } public string TransactionId { get; set; } public string BatchId { get; set; } public string RequestId { get; set; } public string StatusMessage { get; set; } public int StatusCode { get; set; } public string CorrelationId { get; set; } public string CreatedOn { get; set; } public double Amount { get; set; } public string CurrencyCode { get; set; } }

                                    

<?php $transactionId = $_GET['transactionId']; $subscriptionId = $_GET['subscriptionId']; $accountId = $_GET['accountId']; $requestId = $_GET['requestId']; $amount = $_GET['Amount']; $data = json_decode(file_get_contents('php://input'), true); $payload = [ 'AccountId' => $data['AccountId'], 'SubscriptionId' => $data['SubscriptionId'], 'TransactionId' => $data['TransactionId'], 'BatchId' => $data['BatchId'], 'RequestId' => $data['RequestId'], 'StatusMessage' => $data['StatusMessage'], 'StatusCode' => $data['StatusCode'], 'CorrelationId' => $data['CorrelationId'], 'CreatedOn' => $data['CreatedOn'], 'Amount' => $data['Amount'], 'CurrencyCode' => $data['CurrencyCode'] ]; $url = "https://your-url?transactionId=$transactionId&subscriptionId=$subscriptionId&accountId=$accountId&requestId=$requestId&Amount=$amount"; $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($status_code == 200) { // Request was successful. Do something with the response if needed } else { echo "Request failed with status code $status_code"; }

                                    

const https = require('https'); const transactionId = "your-transaction-id"; const subscriptionId = "your-subscription-id"; const accountId = "your-account-id"; const requestId = "your-request-id"; const amount = 100.00; const payload = { AccountId: "string", SubscriptionId: "string", TransactionId: "string", BatchId: "", RequestId: "string", StatusMessage: "string", StatusCode: 0, // 0-success, 1-processing, 2-error, etc. CorrelationId: "string", CreatedOn: new Date().toISOString(), Amount: amount, CurrencyCode: "string" }; const options = { method: 'POST', headers: { 'Content-Type': 'application/json' } }; const url = `https://your-url?transactionId=${transactionId}&subscriptionId=${subscriptionId}&accountId=${accountId}&requestId=${requestId}&Amount=${amount}`; const req = https.request(url, options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode === 200) { // Request was successful. Do something with the response if needed } else { console.log(`Request failed with status code ${res.statusCode}`); } }); }); req.on('error', (error) => { console.error(error); }); req.write(JSON.stringify(payload)); req.end();

Secure your Instant Payment Notifications (IPN) endpoint

Your Instant Payment Notifications (IPN) endpoint will receive transaction confirmations from Xente, so it’s important to ensure that it’s secure. To secure the webhook, we will:

  1. Use symmetric encryption: This means we will exchange keys. We will have your public key and you will have our public key. We must authenticate the keys before completing the transaction. This functionality is crucial and you cannot go live without testing it.

  2. IP whitelisting: As an added layer of security, whitelist the following IPs: 41.2.2.10 and 41.2.2.11