Loading...
Loading...
Configure webhooks for real-time collection notifications.
Last updated: 2026-02-21
Paywize sends payment status updates to your webhook URL whenever a payment status changes. This provides real-time notifications without the need for constant polling of the status endpoint.
POST https://merchant.paywize.in/collection/v1/webhook
Description: Paywize sends payment status updates to the callbackUrl specified during payment initiation when payment status changes.
Webhooks are automatically sent to the callbackUrl you provide when initiating a payment via the /collection/v1/initiate/ API. No separate configuration is needed.
/collection/v1/initiate/, include a callbackUrl in your requestcallbackUrlExample:
// When initiating payment, specify your webhook URL
const paymentData = {
senderId: "TXN123456",
txnType: "INTENT",
requestAmount: "100.50",
callbackUrl: "https://your-website.com/webhook/collection" // ← This URL receives webhooks
};
Ensure your callbackUrl is:
When Paywize sends webhooks to your endpoint, the following headers are included:
Content-Type: application/json
User-Agent: PayWize-Webhook/1.0
{
"data": "Vv9KKQofE6eVVpVtWbEMlRUUeMpXnQ3T3OwD3I4iStD0u85Ntbgv35S6vY8rNb3v7mFW6j2s6gnKA44saJwYOyj4rM1BWXo6TWPsNRpyz40Og1w"
}
{
"senderId": "SENDERID00001",
"txnId": "CFCE160825000001",
"requestAmount": "100.00",
"paymentMode": "Intent",
"utr": "405812345678",
"remarks": "test",
"status": "SUCCESS",
"statusMessage": "Payment completed successfully",
"createdAt": "2025-08-16T07:46:45.277Z",
"updatedAt": "2025-08-16T08:18:15.378Z"
}
| Field | Description |
|---|---|
| senderId | Merchant provided unique sender ID |
| txnId | Paywize generated unique transaction ID |
| requestAmount | Payment amount requested |
| paymentMode | Payment method used (Intent, etc.) |
| utr | Unique Transaction Reference from bank (available for successful payments) |
| remarks | Payment remarks/description |
| status | Current transaction status |
| statusMessage | Detailed status description |
| createdAt | Transaction creation timestamp |
| updatedAt | Last update timestamp |
| Status | Description | When Webhook is Sent |
|---|---|---|
| INITIATED | Payment request created, awaiting customer action | When payment link is generated |
| SUCCESS | Payment completed successfully | When payment is successful |
| FAILED | Payment failed | When payment fails |
| PENDING | Payment under review | When payment is under review |
Your webhook endpoint MUST:
If your webhook endpoint fails to respond properly:
import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.json());
// Decryption function
function decryptMerchantData(data, key, iv) {
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
const decrypted = Buffer.concat([
decipher.update(Buffer.from(data, 'base64')),
decipher.final()
]);
return decrypted.toString('utf8');
}
// Webhook endpoint
app.post('/webhook/paywize/collection', (req, res) => {
try {
const { data } = req.body;
if (!data) {
return res.status(400).json({ error: 'Missing webhook data' });
}
// Decrypt the webhook payload
const decryptedData = decryptMerchantData(data, API_KEY, SECRET_KEY);
const paymentUpdate = JSON.parse(decryptedData);
console.log('Payment update received:', paymentUpdate);
// Process the payment update
processPaymentUpdate(paymentUpdate);
// Respond with 200 OK
res.status(200).json({
status: 'success',
message: 'Webhook processed successfully'
});
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({
status: 'error',
message: 'Failed to process webhook'
});
}
});
function processPaymentUpdate(paymentData) {
const { txnId, status, senderId, utr } = paymentData;
switch (status) {
case 'SUCCESS':
console.log(`Payment ${txnId} completed successfully with UTR: ${utr}`);
// Update your database, send confirmation emails, etc.
updatePaymentStatus(senderId, 'completed', paymentData);
break;
case 'FAILED':
console.log(`Payment ${txnId} failed`);
// Handle failed payment, notify user, etc.
updatePaymentStatus(senderId, 'failed', paymentData);
break;
case 'PENDING':
console.log(`Payment ${txnId} is pending review`);
// Handle pending status
updatePaymentStatus(senderId, 'pending', paymentData);
break;
case 'INITIATED':
console.log(`Payment ${txnId} has been initiated`);
// Payment link generated, ready for customer
updatePaymentStatus(senderId, 'initiated', paymentData);
break;
default:
console.log(`Unknown status ${status} for payment ${txnId}`);
}
}
function updatePaymentStatus(senderId, status, paymentData) {
// Your database update logic here
console.log(`Updating payment ${senderId} to status: ${status}`);
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
from flask import Flask, request, jsonify
import json
from encryption_utils import decrypt_merchant_data
app = Flask(__name__)
@app.route('/webhook/paywize/collection', methods=['POST'])
def handle_webhook():
try:
data = request.json.get('data')
if not data:
return jsonify({'error': 'Missing webhook data'}), 400
# Decrypt the webhook payload
decrypted_data = decrypt_merchant_data(data, API_KEY, SECRET_KEY)
payment_update = json.loads(decrypted_data)
print('Payment update received:', payment_update)
# Process the payment update
process_payment_update(payment_update)
# Respond with 200 OK
return jsonify({
'status': 'success',
'message': 'Webhook processed successfully'
}), 200
except Exception as error:
print('Webhook processing error:', str(error))
return jsonify({
'status': 'error',
'message': 'Failed to process webhook'
}), 500
def process_payment_update(payment_data):
txn_id = payment_data.get('txnId')
status = payment_data.get('status')
sender_id = payment_data.get('senderId')
utr = payment_data.get('utr')
if status == 'SUCCESS':
print(f'Payment {txn_id} completed successfully with UTR: {utr}')
# Update your database, send confirmation emails, etc.
update_payment_status(sender_id, 'completed', payment_data)
elif status == 'FAILED':
print(f'Payment {txn_id} failed')
# Handle failed payment, notify user, etc.
update_payment_status(sender_id, 'failed', payment_data)
elif status == 'PENDING':
print(f'Payment {txn_id} is pending review')
# Handle pending status
update_payment_status(sender_id, 'pending', payment_data)
elif status == 'INITIATED':
print(f'Payment {txn_id} has been initiated')
# Payment link generated, ready for customer
update_payment_status(sender_id, 'initiated', payment_data)
else:
print(f'Unknown status {status} for payment {txn_id}')
def update_payment_status(sender_id, status, payment_data):
# Your database update logic here
print(f'Updating payment {sender_id} to status: {status}')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000, debug=True)
<?php
require_once 'PaywizeEncryption.php';
// Get the webhook payload
$input = file_get_contents('php://input');
$webhookData = json_decode($input, true);
// Set content type
header('Content-Type: application/json');
try {
if (!isset($webhookData['data'])) {
http_response_code(400);
echo json_encode(['error' => 'Missing webhook data']);
exit;
}
// Decrypt the webhook payload
$decryptedData = PaywizeEncryption::decryptMerchantData(
$webhookData['data'],
$apiKey,
$secretKey
);
$paymentUpdate = json_decode($decryptedData, true);
error_log('Payment update received: ' . print_r($paymentUpdate, true));
// Process the payment update
processPaymentUpdate($paymentUpdate);
// Respond with 200 OK
http_response_code(200);
echo json_encode([
'status' => 'success',
'message' => 'Webhook processed successfully'
]);
} catch (Exception $error) {
error_log('Webhook processing error: ' . $error->getMessage());
http_response_code(500);
echo json_encode([
'status' => 'error',
'message' => 'Failed to process webhook'
]);
}
function processPaymentUpdate($paymentData) {
$txnId = $paymentData['txnId'];
$status = $paymentData['status'];
$senderId = $paymentData['senderId'];
$utr = $paymentData['utr'] ?? null;
switch ($status) {
case 'SUCCESS':
error_log("Payment $txnId completed successfully with UTR: $utr");
// Update your database, send confirmation emails, etc.
updatePaymentStatus($senderId, 'completed', $paymentData);
break;
case 'FAILED':
error_log("Payment $txnId failed");
// Handle failed payment, notify user, etc.
updatePaymentStatus($senderId, 'failed', $paymentData);
break;
case 'PENDING':
error_log("Payment $txnId is pending review");
// Handle pending status
updatePaymentStatus($senderId, 'pending', $paymentData);
break;
case 'INITIATED':
error_log("Payment $txnId has been initiated");
// Payment link generated, ready for customer
updatePaymentStatus($senderId, 'initiated', $paymentData);
break;
default:
error_log("Unknown status $status for payment $txnId");
}
}
function updatePaymentStatus($senderId, $status, $paymentData) {
// Your database update logic here
error_log("Updating payment $senderId to status: $status");
}
?>
All webhook payloads are encrypted using AES-256-CBC encryption:
Ensure your webhook endpoint accepts requests only from Paywize IP addresses. Contact support for the current IP whitelist.
Always use HTTPS for webhook URLs to ensure data security in transit.
For local testing, use tools like:
ngrok http 3000 to expose local webhook endpoint{
"data": "test_encrypted_payload_here"
}
function verifyWebhook(webhookData, apiKey, secretKey) {
try {
// Attempt to decrypt the payload
const decryptedData = decryptMerchantData(webhookData.data, apiKey, secretKey);
const paymentData = JSON.parse(decryptedData);
// Verify required fields exist
const requiredFields = ['txnId', 'senderId', 'status', 'statusMessage'];
const isValid = requiredFields.every(field => paymentData.hasOwnProperty(field));
return isValid;
} catch (error) {
console.error('Webhook verification failed:', error);
return false;
}
}
| Issue | Solution |
|---|---|
| Webhook not received | Check URL configuration and server accessibility |
| 500 Internal Server Error | Check server logs and fix code errors |
| Timeout | Ensure response time is under 30 seconds |
| Duplicate processing | Implement idempotency using txnId |
| Decryption failure | Verify API Key and Secret Key are correct |
app.post('/webhook/paywize/collection', (req, res) => {
console.log('Headers:', req.headers);
console.log('Body:', req.body);
// Your webhook processing logic
res.status(200).json({ received: true });
});