Webhooks are an important part of your payment integration. They allow PayziGo notify you about events that happen on your account, such as a charge or payment transaction. A webhook URL is an endpoint on your server where you can receive notifications about such events. When an event occurs, we'll make a POST request to that endpoint, with a JSON body containing the details about the event, including the type of event and the data associated with it.
Here's how to set up a webhook on your PayziGo account:
When enabling webhooks, you have to set a webhook secret. Since webhook URLs are publicly accessible, the webhook secret allows you to verify that incoming requests are from PayziGo. You can specify any value as your secret hash, but we recommend something random. You should also store it as an environment variable on your server. You must specify a webhook secret, as we'll include it in our request to your webhook URL, in a header called Signature. In the webhook endpoint, check if the Signature header is present and that it matches the secret hash you set. If the header is missing, or the value doesn't match, you can discard the request, as it isn't from PayziGo.
To acknowledge receipt of a webhook, your endpoint must return a 200 HTTP status code. Any other response codes, including 3xx codes, will be treated as a failure. We don't care about the response body or headers
Some web frameworks like Rails or Django, automatically check that every POST request contains a CSRF token. This is a useful security feature that protects you and your users from cross-site request forgery.
PHP
// In a Laravel-like app:
Route::post('webhook', function (\Illuminate\Http\Request $request) {
//check for the signature
$secret = '12345';
$signature = $request->header('webhook-secret');
$sign_secret = hash_hmac('sha256', json_encode($request->all()), $secret);
if (!$signature || ($signature !== $sign_secret)) {
// This request isn't from PayziGo; discard
abort(401);
}
$payload = $request->all();
// It's a good idea to log all received events.
Log::info($payload);
// Do something (that doesn't take too long) with the payload
return response(200);
});
node.js
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const app = express();
const PORT = 3000;
// Middleware to parse JSON bodies
app.use(bodyParser.json());
// Logging middleware (similar to Laravel's Log::info)
app.use((req, res, next) => {
console.log('Received payload:', req.body);
next();
});
app.post('/webhook', (req, res) => {
const secret = '12345';
const signature = req.headers['webhook-secret'];
// Create HMAC SHA-256 hash
const signSecret = crypto.createHmac('sha256', secret).update(JSON.stringify(req.body)).digest('hex');
if (!signature || signature !== signSecret) {
// This request isn't from Remittance; discard
return res.status(401).send('Unauthorized');
}
const payload = req.body;
// Log the received payload
console.log('Valid payload:', payload);
// Do something (that doesn't take too long) with the payload
// ...
return res.status(200).send('OK');
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
In a case where PayziGo was unable to reach the URL, all the webhooks will be retried. Webhook URLs must respond with a status 200 OK or the request will be considered unsuccessful and retried.
The JSON payload below is a sample response of a webhook event that gets sent to your webhook URL. You should know that all webhook events across PayziGo, payout, funding, payouts all follow the same payload format as shown below.
Fields | Descriptions |
---|---|
event | The name or type of webhook event that gets sent eg; charge. |
data | The payload of the webhook event object that gets sent. |
{
"event": "swap",
"data": {
"id": "a22eecea-2656-4034-99c5-1b6b3a069ae5",
"amount": "910.76",
"human_readable_amount": 9.107604,
"charge": "0.00",
"human_readable_charge": 0,
"status": "success",
"mode": "live",
"type": "credit",
"decline_reason": null,
"description": "swap",
"to_wallet": "EUR",
"from_wallet": "USD",
"debit_id": "f32d71b6-c921-42b1-973a-52b67cb7adfc",
"created_at": "2024-07-27T11:13:12.000000Z"
}
}
{
"event": "payout",
"data": {
"id": "c5edf3dc-63f1-43d2-a400-d7d295d8297f",
"amount": "9100.00",
"human_readable_amount": 91,
"charge": "142.90",
"human_readable_charge": 1.4290003196954,
"status": "pending",
"mode": "live",
"type": "debit",
"decline_reason": null,
"description": "payout",
"recipient": {
"first_name": "Chioma",
"middle_name": null,
"last_name": "Gladys",
"email": "",
"phone": null,
"amount": "14477461.62",
"human_readable_recipient_amount": 144774.61621623,
"currency": "NGN",
"rate": 1590.92984853,
"transfer_purpose": "Education",
"delivery_method": "bank",
"country": "NG",
"state": null,
"city": "",
"address": null,
"postal_code": null,
"bank": {
"bank_name": "Zenith Bank",
"account_number": "1234567894",
"account_name": "Chioma Gladys",
"account_type": "Savings"
}
},
"sender": {
"id": "120988ea-b91b-4543-9bc0-d851e17db04b",
"first_name": "John",
"middle_name": "Son",
"last_name": "Derrick",
"business_name": null,
"entity": "individual",
"email": "Derrick@remote.com",
"phone": "+2349057550481",
"country": "US",
"state": "DE",
"city": "City",
"address": "Address",
"postal_code": "90000",
"ip_address": "98.97.79.160",
"id_type": "SSN",
"id_number": "213652146416",
"id_url": "https://google.com",
"birth_date": "29-06-1999"
}
}
}
Have a backup strategy in place, in case your webhook endpoint fails. For instance, if your webhook endpoint is throwing server errors, you won't know about any new customer payments because webhook requests will fail.
Your webhook endpoint needs to respond within a certain time limit, or we'll consider it a failure and try again. Avoid doing long-running tasks or network calls in your webhook endpoint so you don't hit the timeout. If your framework supports it, you can have your webhook endpoint immediately return a 200 status code, and then perform the rest of its duties; otherwise, you should dispatch any long-running tasks to a job queue, and then respond.