Overview
When a payment status changes, RohoPay sends a POST request to the callback_url you provided. This is the authoritative notification — more reliable than query parameters in redirect URLs.
Webhook Events
Event Trigger deposit.successfulA mobile money collection or card payment was confirmed withdraw.successfulA disbursement or wallet withdrawal succeeded withdraw.failedA disbursement or withdrawal failed (funds not sent)
Payload
{
"event" : "deposit.successful" ,
"id" : "01j2k3m4n5p6q7r8s9t0uvwx" ,
"internal_reference" : "RHP-2024-ABC123" ,
"provider_reference" : "9876543210" ,
"type" : "collection" ,
"status" : "successful" ,
"payment_method" : "mobile_money" ,
"phone_number" : "256700123456" ,
"amount" : 50000 ,
"currency" : "UGX" ,
"commission_amount" : 500 ,
"net_amount" : 49500 ,
"environment" : "live" ,
"created_at" : "2024-07-15T08:30:00Z" ,
"updated_at" : "2024-07-15T08:31:47Z"
}
Signature Verification
RohoPay signs every outgoing webhook with HMAC-SHA256 . The signature is in the x-rohopay-signature header:
x-rohopay-signature: sha256=abc123def456...
Your webhook secret is visible in Dashboard → Webhooks . Set it as an environment variable on your server:
ROHOPAY_WEBHOOK_SECRET = your-secret-from-dashboard
Verification Code
TypeScript (Next.js App Router)
TypeScript (Express)
Python (FastAPI)
PHP
import crypto from "crypto" ;
export async function POST ( req : Request ) {
const sig = req . headers . get ( "x-rohopay-signature" ) ?? "" ;
const rawBody = await req . text ();
const expected = "sha256=" + crypto
. createHmac ( "sha256" , process . env . ROHOPAY_WEBHOOK_SECRET ! )
. update ( rawBody )
. digest ( "hex" );
if ( ! crypto . timingSafeEqual ( Buffer . from ( sig ), Buffer . from ( expected ))) {
return new Response ( "Unauthorized" , { status: 401 });
}
const event = JSON . parse ( rawBody );
if ( event . event === "deposit.successful" ) {
// Fulfil order, credit customer account, etc.
await creditAccount ( event . internal_reference , event . net_amount );
}
if ( event . event === "withdraw.failed" ) {
// Notify customer that payout failed
await notifyCustomer ( event . internal_reference );
}
return new Response ( "OK" , { status: 200 });
}
Delivery Requirements
Return HTTP 200–299 within 10 seconds
If your endpoint times out or returns a non-2xx code, RohoPay retries with exponential backoff
Process heavy logic asynchronously: return 200 first, then process in a background job
View Webhook Config in Dashboard
Go to Dashboard → Webhooks to:
Copy your webhook secret
See the signature header name (x-rohopay-signature)
Test your webhook endpoint
Testing Locally
Use a tunnel to expose your local server during development:
# ngrok
ngrok http 3000
# Cloudflare
cloudflared tunnel --url http://localhost:3000
Use the tunnel URL as your callback_url in test-mode requests.
Always verify the signature before processing the event. Never skip verification — even in development mode. An unverified webhook handler is a security vulnerability.