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:
You POST a transaction to Xente.
We queue it up.
We process the transaction - this might involve user approval.
We call back your webhook to notify you of the final status of the transaction.
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:
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.
JSON parsing: Ensure that your IPN URL can parse JSON data. This means extracting relevant information from the incoming request payload.
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.
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:
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.
IP whitelisting: As an added layer of security, whitelist the following IPs: 41.2.2.10 and 41.2.2.11