Webhooks & Callbacks

Receive real-time notifications about payment status changes through secure webhooks.

Overview

Webhooks allow LakiPay to send real-time notifications to your application when payment events occur. This enables you to update your system automatically without polling the API.

Event Types

DEPOSIT, WITHDRAWAL

Security

RSA-2048 signature verification

Webhook Events

Event Types

DEPOSIT

Triggered when a payment is received or its status changes

WITHDRAWAL

Triggered when a withdrawal is processed or its status changes

Webhook Payload Structure

{
  "event": "DEPOSIT",
  "transaction_id": "TXN-123456789",
  "reference": "ORDER-12345",
  "amount": 100.00,
  "currency": "ETB",
  "status": "SUCCESS",
  "medium": "MPESA",
  "timestamp": "2024-01-15T10:30:00Z",
  "signature": "base64-encoded-signature"
}

Status Values

SUCCESS

Payment completed successfully

FAILED

Payment failed

PENDING

Payment is being processed

CANCELLED

Payment was cancelled

Signature Verification

Security Requirement

Always verify webhook signatures to ensure requests are from LakiPay. Never process webhooks without signature verification.

RSA-2048 Encryption

LakiPay uses RSA-2048 encryption for webhook signatures. The signature is included in the webhook payload and must be verified using our public key.

Signature Algorithm: RSA-2048
Signature Format: Base64 encoded
Public Key: Available in your dashboard

Verification Process

  1. 1Extract the signature from the webhook payload
  2. 2Create a canonical string from the payload (excluding the signature field)
  3. 3Decode the base64 signature
  4. 4Verify using RSA-2048 public key
  5. 5Process the webhook only if verification succeeds

Canonical String Creation

The canonical string is created by:

  1. 1. Sort all fields alphabetically by key
  2. 2. Concatenate key-value pairs: key=value
  3. 3. Join with & separator
  4. 4. Exclude the signature field
Example:
amount=100.00&currency=ETB&event=DEPOSIT&
reference=ORDER-12345&status=SUCCESS&
timestamp=2024-01-15T10:30:00Z&
transaction_id=TXN-123456789

Verification Examples

Node.js

const crypto = require('crypto');

function verifyWebhook(payload, signature, publicKey) {
  // Create canonical string
  const canonical = Object.keys(payload)
    .filter(key => key !== 'signature')
    .sort()
    .map(key => `${key}=${payload[key]}`)
    .join('&');
  
  // Verify signature
  const verify = crypto.createVerify('RSA-SHA256');
  verify.update(canonical);
  return verify.verify(publicKey, signature, 'base64');
}

Python

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
import base64

def verify_webhook(payload, signature, public_key):
    # Create canonical string
    canonical = '&'.join(
        f"{k}={v}" for k, v in sorted(
            {k: v for k, v in payload.items() if k != 'signature'}.items()
        )
    )
    
    # Verify signature
    signature_bytes = base64.b64decode(signature)
    public_key.verify(
        signature_bytes,
        canonical.encode(),
        padding.PKCS1v15(),
        hashes.SHA256()
    )
    return True

Best Practices

Always Verify Signatures

Never process webhooks without verifying the signature first

Use HTTPS

Always use HTTPS endpoints for webhook URLs

Idempotency

Handle duplicate webhooks using transaction_id as idempotency key

Timeout Handling

Respond quickly (within 5 seconds) to webhook requests

Logging

Log all webhook events for debugging and auditing

Error Handling

Return appropriate HTTP status codes (200 for success, 4xx/5xx for errors)