What are Webhooks?
Webhooks are HTTP callbacks that FossaPay sends to your server when events occur in your account. They enable you to receive real-time notifications about payments, payouts, settlements, and other important events.
Webhooks eliminate the need for polling and ensure your application stays in sync with FossaPay in real-time.
Why Use Webhooks?
Real-time Updates Get notified immediately when events occur
Reduce Polling No need to constantly check for updates
Automated Workflows Trigger actions automatically based on events
Better UX Update your users instantly about transaction status
Webhook Events
Deposit Events
Event Description When it fires deposit.completedDeposit completed successfully When a deposit is successfully processed
Withdrawal Events
Event Description When it fires withdrawal.completedWithdrawal completed successfully When a withdrawal is successfully processed
Transfer Events
Event Description When it fires transfer.completedTransfer completed successfully When a transfer is successfully processed transfer.failedTransfer failed When a transfer fails to process
Wallet Events
Event Description When it fires wallet.balance.updatedWallet balance updated When a wallet balance is updated
Setting Up Webhooks
Set your webhook endpoint in the dashboard or via API:
Via Dashboard:
Go to Settings → Webhooks
Enter your webhook URL
Select events to subscribe to
Save configuration
Via API:
await fossapay . webhooks . configure ({
url: 'https://your-app.com/webhooks/fossapay' ,
events: [
'deposit.completed' ,
'withdrawal.completed' ,
'transfer.completed' ,
'transfer.failed' ,
'wallet.balance.updated'
],
secret: 'your-webhook-secret' // Optional: for signature verification
});
Webhook URL Requirements
Your webhook endpoint must:
Use HTTPS (HTTP not allowed in production)
Respond within 30 seconds
Return a 2xx status code for successful receipt
Handle duplicate events gracefully
Webhook URLs must be publicly accessible. Use services like ngrok for local development.
Webhook Payload
All webhooks follow this structure:
{
"event" : "deposit.completed" ,
"event_id" : "evt_abc123xyz" ,
"timestamp" : "2024-01-15T10:30:45Z" ,
"data" : {
"transaction_id" : "txn_001" ,
"customer_id" : "cus_123" ,
"sender" : {
"address" : "0x123..." ,
"chain" : "Ethereum"
},
"recipient" : {
"address" : "0x456..." ,
"chain" : "Ethereum"
},
"amount" : 1000000 ,
"currency" : "USDT" ,
"reference" : "dep-20240115-001" ,
"status" : "completed" ,
"transaction_type" : "deposit" ,
"blockchain" : "Ethereum" ,
"transaction_hash" : "0x789..." ,
"explorer_link" : "https://etherscan.io/tx/0x789..." ,
"timestamp" : "2024-01-15T10:30:40Z"
},
"business_id" : "bus_your_business"
}
Implementing Webhook Handler
Basic Handler (Node.js/Express)
const express = require ( 'express' );
const crypto = require ( 'crypto' );
const app = express ();
app . use ( express . json ());
app . post ( '/webhooks/fossapay' , async ( req , res ) => {
// 1. Verify webhook signature
const signature = req . headers [ 'x-fossapay-signature' ];
const secret = process . env . FOSSAPAY_WEBHOOK_SECRET ;
if ( ! verifySignature ( req . body , signature , secret )) {
return res . status ( 401 ). send ( 'Invalid signature' );
}
// 2. Respond quickly (within 30 seconds)
res . status ( 200 ). send ( 'Webhook received' );
// 3. Process webhook asynchronously
processWebhook ( req . body ). catch ( err => {
console . error ( 'Webhook processing failed:' , err );
});
});
async function processWebhook ( payload ) {
const { event , data } = payload ;
switch ( event ) {
case 'deposit.completed' :
await handleDepositCompleted ( data );
break ;
case 'withdrawal.completed' :
await handleWithdrawalCompleted ( data );
break ;
case 'transfer.completed' :
await handleTransferCompleted ( data );
break ;
case 'transfer.failed' :
await handleTransferFailed ( data );
break ;
case 'wallet.balance.updated' :
await handleWalletBalanceUpdated ( data );
break ;
default :
console . log ( 'Unhandled event:' , event );
}
}
async function handleDepositCompleted ( data ) {
// Credit customer wallet
await db . transactions . create ({
id: data . transaction_id ,
customer_id: data . customer_id ,
amount: data . amount ,
type: 'credit' ,
status: 'completed'
});
await db . wallets . increment ({
customer_id: data . customer_id ,
amount: data . amount
});
// Notify customer
await sendNotification ( data . customer_id , {
title: 'Deposit Received' ,
message: `Your wallet has been credited with ${ data . amount } ${ data . currency } `
});
}
function verifySignature ( payload , signature , secret ) {
const hash = crypto
. createHmac ( 'sha256' , secret )
. update ( JSON . stringify ( payload ))
. digest ( 'hex' );
return crypto . timingSafeEqual (
Buffer . from ( signature , 'hex' ),
Buffer . from ( hash , 'hex' )
);
}
app . listen ( 3000 );
Python/Flask Example
from flask import Flask, request, jsonify
import hashlib
import hmac
import json
app = Flask( __name__ )
@app.route ( '/webhooks/fossapay' , methods = [ 'POST' ])
def handle_webhook ():
# Verify signature
signature = request.headers.get( 'X-FossaPay-Signature' )
secret = os.environ[ 'FOSSAPAY_WEBHOOK_SECRET' ]
if not verify_signature(request.data, signature, secret):
return 'Invalid signature' , 401
# Respond quickly
payload = request.json
# Process asynchronously
process_webhook.delay(payload) # Using Celery
return 'Webhook received' , 200
def verify_signature ( payload , signature , secret ):
hash = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest( hash , signature)
def process_webhook ( payload ):
event = payload[ 'event' ]
data = payload[ 'data' ]
if event == 'deposit.completed' :
handle_deposit_completed(data)
elif event == 'withdrawal.completed' :
handle_withdrawal_completed(data)
elif event == 'transfer.completed' :
handle_transfer_completed(data)
elif event == 'transfer.failed' :
handle_transfer_failed(data)
elif event == 'wallet.balance.updated' :
handle_wallet_balance_updated(data)
Security
Signature Verification
FossaPay signs all webhooks with HMAC SHA256:
const crypto = require ( 'crypto' );
function verifyWebhookSignature ( payload , signature , secret ) {
const hash = crypto
. createHmac ( 'sha256' , secret )
. update ( JSON . stringify ( payload ))
. digest ( 'hex' );
return crypto . timingSafeEqual (
Buffer . from ( hash ),
Buffer . from ( signature )
);
}
IP Whitelisting
Optionally restrict webhooks to FossaPay IPs:
52.49.173.169
54.171.127.99
52.214.14.220
Use IP whitelisting as an additional layer of security, not as a replacement for signature verification.
Retry Logic
If your endpoint doesn’t respond with a 2xx status, FossaPay retries:
Attempt Delay 1st retry 5 minutes 2nd retry 30 minutes 3rd retry 2 hours 4th retry 6 hours 5th retry 24 hours
After 5 failed attempts, the webhook is marked as failed and no more retries occur.
Check your webhook logs in the dashboard for failed deliveries and investigate issues promptly.
Idempotency
Webhooks may be delivered more than once. Implement idempotent handling:
async function handleDepositCompleted ( data ) {
const existingTransaction = await db . transactions . findOne ({
id: data . transaction_id
});
if ( existingTransaction ) {
console . log ( 'Transaction already processed' );
return ; // Skip duplicate
}
// Process deposit
await db . transactions . create ({
id: data . transaction_id ,
// ... other fields
});
}
Testing Webhooks
Local Testing with ngrok
# Start ngrok
ngrok http 3000
# Use the HTTPS URL in FossaPay dashboard
# Example: https://abc123.ngrok.io/webhooks/fossapay
Simulate Webhooks
Test your webhook handler with simulated events:
curl -X POST https://api-staging.fossapay.com/api/v1/webhooks/simulate \
-H "Authorization: Bearer fp_test_sk_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"event": "deposit.completed",
"customer_id": "cus_123",
"amount": 1000000,
"currency": "USDT"
}'
Webhook Logs
View webhook delivery logs in your dashboard:
Go to Settings → Webhooks
Click on “Webhook Logs”
Filter by status, event type, or date
Replay failed webhooks
Best Practices
Always return 200 immediately, then process asynchronously: app . post ( '/webhooks' , ( req , res ) => {
res . status ( 200 ). send ( 'OK' );
processAsync ( req . body ); // Don't await
});
Never trust webhooks without signature verification: if ( ! verifySignature ( payload , signature , secret )) {
return res . status ( 401 ). send ( 'Unauthorized' );
}
Use event_id or transaction_id to detect duplicates: const processed = await redis . get ( `webhook: ${ event_id } ` );
if ( processed ) return ;
await redis . set ( `webhook: ${ event_id } ` , '1' , 'EX' , 86400 );
Log all webhook receipts for debugging and auditing: logger . info ( 'Webhook received' , {
event_id: payload . event_id ,
event: payload . event ,
timestamp: payload . timestamp
});
Set up alerts for webhook failures: if ( failureCount > threshold ) {
await sendAlert ( 'Webhook failures detected' );
}
Webhook Payload Examples
Deposit Completed (Crypto)
{
"event" : "deposit.completed" ,
"event_id" : "evt_abc123" ,
"timestamp" : "2024-01-15T10:30:45Z" ,
"data" : {
"transaction_id" : "txn_001" ,
"customer_id" : "cus_123" ,
"sender" : {
"address" : "0x123..." ,
"chain" : "Ethereum"
},
"recipient" : {
"address" : "0x456..." ,
"chain" : "Ethereum"
},
"amount" : 1000000 ,
"currency" : "USDT" ,
"reference" : "dep-20240115-001" ,
"status" : "completed" ,
"transaction_type" : "deposit" ,
"blockchain" : "Ethereum" ,
"transaction_hash" : "0x789..." ,
"explorer_link" : "https://etherscan.io/tx/0x789..." ,
"timestamp" : "2024-01-15T10:30:40Z"
}
}
Deposit Completed (Fiat)
{
"event" : "deposit.completed" ,
"event_id" : "evt_def456" ,
"timestamp" : "2024-01-15T11:00:00Z" ,
"data" : {
"transaction_id" : "txn_002" ,
"customer_id" : "cus_456" ,
"sender" : {
"account_name" : "John Doe" ,
"account_number" : "0987654321" ,
"bank_name" : "GTBank"
},
"recipient" : {
"account_name" : "Jane Smith" ,
"account_number" : "0123456789" ,
"bank_name" : "GTBank"
},
"amount" : 50000 ,
"currency" : "NGN" ,
"reference" : "dep-20240115-002" ,
"status" : "completed" ,
"transaction_type" : "deposit" ,
"notes" : "Bank transfer deposit" ,
"timestamp" : "2024-01-15T11:00:00Z"
}
}
Withdrawal Completed
{
"event" : "withdrawal.completed" ,
"event_id" : "evt_xyz789" ,
"timestamp" : "2024-01-15T11:45:23Z" ,
"data" : {
"transaction_id" : "txn_003" ,
"customer_id" : "cus_789" ,
"recipient" : {
"account_name" : "Jane Smith" ,
"account_number" : "0123456789" ,
"bank_name" : "GTBank"
},
"amount" : 25000 ,
"currency" : "NGN" ,
"reference" : "wth-20240115-001" ,
"status" : "completed" ,
"transaction_type" : "withdrawal" ,
"timestamp" : "2024-01-15T11:45:20Z"
}
}
Transfer Completed
{
"event" : "transfer.completed" ,
"event_id" : "evt_transfer1" ,
"timestamp" : "2024-01-15T12:30:00Z" ,
"data" : {
"transaction_id" : "txn_004" ,
"customer_id" : "cus_123" ,
"sender" : {
"address" : "0x123..." ,
"chain" : "Ethereum"
},
"recipient" : {
"address" : "0x456..." ,
"chain" : "Ethereum"
},
"amount" : 500000 ,
"currency" : "USDT" ,
"reference" : "trf-20240115-001" ,
"status" : "completed" ,
"transaction_type" : "transfer" ,
"blockchain" : "Ethereum" ,
"transaction_hash" : "0xabc..." ,
"timestamp" : "2024-01-15T12:30:00Z"
}
}
Transfer Failed
{
"event" : "transfer.failed" ,
"event_id" : "evt_transfer2" ,
"timestamp" : "2024-01-15T13:15:00Z" ,
"data" : {
"transaction_id" : "txn_005" ,
"customer_id" : "cus_456" ,
"sender" : {
"address" : "0x789..." ,
"chain" : "Ethereum"
},
"recipient" : {
"address" : "0xabc..." ,
"chain" : "Ethereum"
},
"amount" : 300000 ,
"currency" : "USDT" ,
"reference" : "trf-20240115-002" ,
"status" : "failed" ,
"transaction_type" : "transfer" ,
"blockchain" : "Ethereum" ,
"transaction_hash" : "0xdef..." ,
"timestamp" : "2024-01-15T13:15:00Z"
}
}
Wallet Balance Updated
{
"event" : "wallet.balance.updated" ,
"event_id" : "evt_balance1" ,
"timestamp" : "2024-01-15T14:00:00Z" ,
"data" : {
"from_account" : {
"id" : "acc_001" ,
"account_number" : "0123456789" ,
"account_name" : "Main Wallet" ,
"bank_name" : "GTBank" ,
"customer_id" : "cus_123"
},
"to_account" : {
"id" : "acc_002" ,
"account_number" : "0987654321" ,
"account_name" : "Savings Wallet" ,
"bank_name" : "GTBank" ,
"customer_id" : "cus_123"
},
"amount" : 25000 ,
"currency" : "NGN" ,
"reference" : "bal-20240115-001" ,
"narration" : "Transfer between accounts" ,
"timestamp" : "2024-01-15T14:00:00Z"
}
}
Next Steps
Configure Webhooks Set up your webhook endpoint
View Webhook Logs Monitor webhook deliveries
Collections Guide Handle payment webhooks
Payouts Guide Handle payout webhooks