This documentation page provides information on webhooks, which are HTTP callbacks that allow your application to receive real-time notifications when events occur on the Ubiqpay platform.
Whenever the status of a payment changes to succeeded
or failed
on the Ubiqpay platform, a callback request is sent to the merchant system using the HTTP method POST
.
The Ubiqpay platform is configured to use a retry mechanism with a back-off strategy. Specifically, the configuration specifies a maximum of 10 retries and a duration of 5 seconds between retries.
This means that if a network request made by the platform fails, the Ubiqpay platform will automatically retry the request up to 10 times. If the retry fails, the platform will wait 5 seconds before attempting the next retry. The duration between retries increases exponentially based on the number of retries that have already been attempted, following a backoff strategy.
This retry mechanism can help improve the reliability and resiliency of network requests by automatically handling transient errors and network disruptions. By configuring the maximum number of retries and the duration between retries, the application can control how aggressively it retries requests and how long it waits before giving up on a request.
Below is an example of a body request sent by the Ubiqpay platform:
Collections
// POST /<callback_path>
{
"amount": 4000.0,
"currency": "KES",
"email": "[email protected]",
"msisdn": "+254712345678",
"narration": "T2023111612475973225",
"country_code": "KE",
"first_name": "John",
"last_name": "Smith",
"language": "EN",
"merchant_reference": "T2023111612475973225",
"transaction_id": "e29f997c031a41dc8bf4",
"payment_method": "MOBILE_MONEY",
"payment_status": "succeeded",
"created_at": "2023-11-16T12:47:59.000",
"updated_at": "2023-11-16T12:48:24.039"
}
Payouts
{
"amount": 1522.0,
"currency": "KES",
"msisdn": "+254712345678",
"narration": "T2023111612475973225",
"fees": 152.360217,
"country": "KE",
"first_name": "John",
"last_name": "Smith",
"merchant_reference": "T2023111612475973225",
"transaction_id": "8d771a2a62fd43cbbd7f",
"payout_method": "MOBILE_MONEY",
"status": "succeeded",
"created_at": "2023-11-16T12:24:53.000",
"updated_at": "2023-11-16T12:27:13.106"
}
API Request Signature Verification
To ensure the integrity and authenticity of the API requests, we sign the JSON payloads with our private key using the RSA-SHA256
algorithm. The generated signature is included in the X-Signature
header of the HTTP request. As a client, you should verify this signature using our public key to ensure the request's data has not been tampered with during transmission.
Signature Verification Process
Follow these steps to verify the signature of an API request:
- Extract the JSON payload and the
X-Signature
header value from the incoming request. - Use our provided public key to verify the signature against the original JSON payload.
Code Example
The following example demonstrates how to verify the signature:
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
public class SignatureUtil {
public static boolean verifySignature(String payload, String signature, PublicKey publicKey)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature publicSignature = Signature.getInstance("SHA256withRSA");
publicSignature.initVerify(publicKey);
publicSignature.update(payload.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = Base64.getDecoder().decode(signature);
return publicSignature.verify(signatureBytes);
}
}
import java.io.StringReader;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
public class KeyUtil {
public static PublicKey loadPublicKey(String publicKeyContent) throws NoSuchAlgorithmException, InvalidKeySpecException {
publicKeyContent = publicKeyContent
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] keyBytes = Base64.getDecoder().decode(publicKeyContent);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
}
String payload = "your_received_payload_here";
String signature = "your_received_signature_here";
String publicKeyContent = "our_provided_public_key_content_here";
try {
PublicKey publicKey = KeyUtil.loadPublicKey(publicKeyContent);
boolean isValid = SignatureUtil.verifySignature(payload, signature, publicKey);
System.out.println("Signature is valid: " + isValid);
} catch (Exception e) {
e.printStackTrace();
}
<?php
function verifySignature($payload, $signature, $publicKey)
{
// Decode the signature from Base64
$signature = base64_decode($signature);
// Create a digest for comparison
$digest = openssl_digest($payload, 'sha256', TRUE);
// Verify the signature
$verification = openssl_verify($payload, $signature, $publicKey, OPENSSL_ALGO_SHA256);
return $verification === 1;
}
function loadPublicKey($publicKeyContent)
{
// Clean the public key content
$publicKeyContent = str_replace("-----BEGIN PUBLIC KEY-----", "", $publicKeyContent);
$publicKeyContent = str_replace("-----END PUBLIC KEY-----", "", $publicKeyContent);
$publicKeyContent = str_replace(" ", "", $publicKeyContent); // Removes whitespace
$publicKeyContent = str_replace("\n", "", $publicKeyContent); // Removes newlines
$publicKeyContent = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($publicKeyContent, 64, "\n") . "-----END PUBLIC KEY-----";
// Convert the public key to a resource
$publicKey = openssl_pkey_get_public($publicKeyContent);
if (!$publicKey) {
throw new Exception("Public key is not valid");
}
return $publicKey;
}
// Usage example
$payload = "your_received_payload_here";
$signature = "your_received_signature_here";
$publicKeyContent = "our_provided_public_key_content_here";
try {
$publicKey = loadPublicKey($publicKeyContent);
$isValid = verifySignature($payload, $signature, $publicKey);
echo "Signature is valid: " . ($isValid ? 'true' : 'false');
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
const crypto = require('crypto');
const fs = require('fs');
function verifySignature(payload, signature, publicKeyPath) {
try {
// Load the public key file
const publicKeyContent = fs.readFileSync(publicKeyPath, 'utf8').trim();
const publicKey = crypto.createPublicKey({
key: publicKeyContent,
format: 'pem',
type: 'spki',
});
// Decode signature from Base64
const signatureBuffer = Buffer.from(signature, 'base64');
// Verify the signature
const verifier = crypto.createVerify('SHA256');
verifier.update(Buffer.from(payload, 'utf8'));
verifier.end();
return verifier.verify(publicKey, signatureBuffer);
} catch (error) {
console.error('Verification error:', error.message);
return false;
}
}
// Example usage
const payload = "your_received_payload_here";
const signature = "your_received_signature_here";
const publicKeyPath = './public_key.pem'; // Ensure this is correct
try {
const isValid = verifySignature(payload, signature, publicKeyPath);
console.log('Signature is valid:', isValid);
} catch (error) {
console.error('Error:', error.message);
}
Replace the placeholders with the actual payload, signature, and public key content. This code snippet will print whether the signature is valid or not.
Public Key
Our public key for signature verification is provided below:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqpa2HiNvbZWUPmFBiyaS
jQjhJfu6dV6cZENDaA1rWzg5i3uu2Rm1DWJdv+kgn6MUCyHn1Fyagv5RB40ytCNh
X6Q1sb2aeR9PivLct6+KhYUHUp2FmsVYSUQYuHQBplMiL/MifrZMWkLE9tRvo5oc
IdYArZvtTcsbqaLniNvnOinktI8ISpoDNgwR0OdhkHjgMluLpIHARVrHuqR9HFXX
kuJbiij0c8Tz2DObyIeoUjxi6Q5oDlGNym4bOHAhE+F697B+najC8LuBHRBelmge
fpU5scO5sUdOgVs5A8RJDMLMU2bPqA5TBSqWDAcPZwdGIgT7rh+Sf5hP7NVdi6/B
sQIDAQAB
-----END PUBLIC KEY-----
Important Notes
- Always verify the signature before processing the request.
- If the signature is invalid, reject the request, as it may have been tampered with or originated from an unauthorized source.