Webhooks
Getting started with Webhooks
Webhooks
are an essential part of your payment integration. They allow QorePay to notify you about events on your account, such as a successful payment or a failed 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 event's details, including the event's type and the associated data.
When to use Webhooks
Webhooks
are supported for all kinds of payment methods, but they're especially useful for methods and events that happen outside your application's control, such as:
- Getting paid via a card
- a customer being charged for their subscription (recurring payments)
- a pending payment transitions to successful (bank transfers)
These are all asynchronous actions—your application does not control them, so you won't know when they are completed unless we notify you or check later.
Setting up a webhook
allows us to notify you when these payments are completed. Within your webhook endpoint, you can then:
- Update a customer's membership records in your database when a subscription payment succeeds.
- Email a customer when a subscription payment fails.
- Update your order records when a pending payment status is updated to successful.
Webhook Retry logic
Our callbacks
have a retry mechanism that uses exponential backoff.
The last retry attempt is made at +36h of the first webhook.
Webhook Structure
Card Payment success
{
"id": "dd1d0995-1105-4086-ab81-40da2d831325",
"due": 1663329928,
"type": "purchase",
"client": {
"cc": [],
"bcc": [],
"city": "",
"email": "[email protected]",
"phone": "",
"country": "",
"zip_code": "",
"bank_code": "",
"full_name": "",
"brand_name": "",
"legal_name": "",
"tax_number": "",
"client_type": null,
"bank_account": "",
"personal_code": "",
"shipping_city": "",
"street_address": "",
"shipping_country": "",
"shipping_zip_code": "",
"registration_number": "",
"shipping_street_address": ""
},
"issued": "2022-09-16",
"status": "paid",
"is_test": true,
"payment": {
"amount": 100,
"paid_on": 1663326625,
"currency": "ZAR",
"fee_amount": 0,
"net_amount": 90,
"description": "",
"is_outgoing": false,
"payment_type": "purchase",
"pending_amount": 10,
"remote_paid_on": 1663326625,
"owned_bank_code": null,
"owned_bank_account": null,
"pending_unfreeze_on": 1663585825,
"owned_bank_account_id": null
},
"product": "purchases",
"user_id": null,
"brand_id": "f880ebd4-04fb-4b88-8531-7ca148c93f2e",
"order_id": null,
"platform": "api",
"purchase": {
"debt": 0,
"notes": "",
"total": 100,
"currency": "NGN",
"language": "en",
"products": [
{
"name": "test",
"price": 100,
"category": "",
"discount": 0,
"quantity": "1.0000",
"tax_percent": "0.00"
}
],
"timezone": "UTC",
"due_strict": false,
"email_message": "",
"total_override": null,
"shipping_options": [],
"subtotal_override": null,
"total_tax_override": null,
"payment_method_details": {},
"request_client_details": [],
"total_discount_override": null
},
"client_id": null,
"reference": "",
"viewed_on": 1663326561,
"company_id": "492c861c-a64f-424f-8edf-9165667dc526",
"created_on": 1663326328,
"event_type": "purchase.paid",
"updated_on": 1663326625,
"invoice_url": null,
"checkout_url": "https://gate.qorepay.com/p/dd1d0995-1105-4086-ab81-40da2d831325/invoice/",
"send_receipt": false,
"skip_capture": false,
"creator_agent": "",
"issuer_details": {
"website": "",
"brand_name": "ACME Insurance",
"legal_city": "",
"legal_name": "ACME Insurance",
"tax_number": "",
"bank_accounts": [
{
"bank_code": "",
"bank_account": ""
}
],
"legal_country": "NG",
"legal_zip_code": "",
"registration_number": "",
"legal_street_address": ""
},
"marked_as_paid": false,
"status_history": [
{
"status": "created",
"timestamp": 1663326328
},
{
"status": "viewed",
"timestamp": 1663326561
},
{
"status": "pending_execute",
"timestamp": 1663326621
},
{
"status": "paid",
"timestamp": 1663326625
}
],
"cancel_redirect": "",
"created_from_ip": "102.65.48.194",
"direct_post_url": null,
"force_recurring": false,
"recurring_token": null,
"failure_redirect": "",
"success_callback": "",
"success_redirect": "",
"transaction_data": {
"flow": "payform",
"extra": {
"card_type": "debit",
"card_brand": "mastercard",
"masked_pan": "555555******4444",
"card_issuer": "ciagroup",
"expiry_year": 22,
"expiry_month": 12,
"cardholder_name": "Test",
"card_issuer_country": "BR"
},
"country": "BR",
"attempts": [
{
"flow": "payform",
"type": "execute",
"error": null,
"extra": {
"card_type": "debit",
"card_brand": "mastercard",
"masked_pan": "555555******4444",
"card_issuer": "ciagroup",
"expiry_year": 22,
"expiry_month": 12,
"cardholder_name": "Test",
"card_issuer_country": "BR"
},
"country": "BR",
"client_ip": "102.65.48.194",
"fee_amount": 10,
"successful": true,
"payment_method": "mastercard",
"processing_time": 1663326625
}
],
"payment_method": "mastercard"
},
"refundable_amount": 100,
"is_recurring_token": false,
"billing_template_id": null,
"currency_conversion": null,
"reference_generated": "RV2",
"refund_availability": "all",
"payment_method_whitelist": null
}
Card Payment failure
{
"id": "3d6ae45d-1525-4b03-9206-0b6ff82bf6f1",
"due": 1663327973,
"type": "purchase",
"client": {
"cc": [],
"bcc": [],
"city": "",
"email": "[email protected]",
"phone": "",
"country": "",
"zip_code": "",
"bank_code": "",
"full_name": "",
"brand_name": "",
"legal_name": "",
"tax_number": "",
"client_type": null,
"bank_account": "",
"personal_code": "",
"shipping_city": "",
"street_address": "",
"shipping_country": "",
"shipping_zip_code": "",
"registration_number": "",
"shipping_street_address": ""
},
"issued": "2022-09-16",
"status": "error",
"is_test": true,
"payment": null,
"product": "purchases",
"user_id": null,
"brand_id": "f880ebd4-04fb-4b88-8531-7ca148c93f2e",
"order_id": null,
"platform": "api",
"purchase": {
"debt": 0,
"notes": "",
"total": 100,
"currency": "NGN",
"language": "en",
"products": [
{
"name": "test",
"price": 100,
"category": "",
"discount": 0,
"quantity": "1.0000",
"tax_percent": "0.00"
}
],
"timezone": "UTC",
"due_strict": false,
"email_message": "",
"total_override": null,
"shipping_options": [],
"subtotal_override": null,
"total_tax_override": null,
"payment_method_details": {},
"request_client_details": [],
"total_discount_override": null
},
"client_id": null,
"reference": "",
"viewed_on": 1663326689,
"company_id": "492c861c-a64f-424f-8edf-9165667dc526",
"created_on": 1663324373,
"event_type": "purchase.payment_failure",
"updated_on": 1663326852,
"invoice_url": null,
"checkout_url": "https://payments.qorepay.com/p/3d6ae45d-1525-4b03-9206-0b6ff82bf6f1/",
"send_receipt": false,
"skip_capture": false,
"creator_agent": "",
"issuer_details": {
"website": "",
"brand_name": "ACME Insurance",
"legal_city": "",
"legal_name": "ACME Insurance",
"tax_number": "",
"bank_accounts": [
{
"bank_code": "",
"bank_account": ""
}
],
"legal_country": "NG",
"legal_zip_code": "",
"registration_number": "",
"legal_street_address": ""
},
"marked_as_paid": false,
"status_history": [
{
"status": "created",
"timestamp": 1663324373
},
{
"status": "viewed",
"timestamp": 1663326689
},
{
"status": "pending_execute",
"timestamp": 1663326850
},
{
"status": "error",
"timestamp": 1663326852
}
],
"cancel_redirect": "",
"created_from_ip": "102.65.48.194",
"direct_post_url": null,
"force_recurring": false,
"recurring_token": null,
"failure_redirect": "",
"success_callback": "",
"success_redirect": "",
"transaction_data": {
"flow": "payform",
"extra": {
"card_type": "debit",
"card_brand": "mastercard",
"card_issuer": "ciagroup",
"card_issuer_country": "BR"
},
"country": "",
"attempts": [
{
"flow": "payform",
"type": "execute",
"error": {
"code": "general_transaction_error",
"message": "Unrecognized transaction error"
},
"extra": {
"card_type": "debit",
"card_brand": "mastercard",
"masked_pan": "555555******4444",
"card_issuer": "ciagroup",
"expiry_year": 22,
"expiry_month": 11,
"cardholder_name": "test",
"card_issuer_country": "BR"
},
"country": "BR",
"client_ip": "102.65.48.194",
"fee_amount": 20,
"successful": false,
"payment_method": "mastercard",
"processing_time": 1663326852
}
],
"payment_method": ""
},
"refundable_amount": 0,
"is_recurring_token": false,
"billing_template_id": null,
"currency_conversion": null,
"reference_generated": "RV1",
"refund_availability": "none",
"payment_method_whitelist": null
}
Create Invoice
{
"id": "d1b8e94e-6908-44e0-9724-2a3d6240bd0b",
"due": 1663931735,
"type": "purchase",
"client": {
"cc": [],
"bcc": [],
"city": "",
"email": "[email protected]",
"phone": "",
"country": "",
"zip_code": "",
"bank_code": "",
"full_name": "Test",
"brand_name": "",
"legal_name": "",
"tax_number": "",
"client_type": null,
"bank_account": "",
"personal_code": "",
"shipping_city": "",
"street_address": "",
"shipping_country": "",
"shipping_zip_code": "",
"registration_number": "",
"shipping_street_address": ""
},
"issued": "2022-09-16",
"status": "created",
"is_test": true,
"payment": null,
"product": "billing_invoices",
"user_id": "44744012-b260-4cf1-a910-f692c98e893f",
"brand_id": "f880ebd4-04fb-4b88-8531-7ca148c93f2e",
"order_id": null,
"platform": "web",
"purchase": {
"debt": 0,
"notes": "",
"total": 1000,
"currency": "NGN",
"language": "en",
"products": [
{
"name": "Test",
"price": 1000,
"category": "",
"discount": 0,
"quantity": "1.0000",
"tax_percent": "0.00"
}
],
"timezone": "UTC",
"due_strict": false,
"email_message": "",
"total_override": null,
"shipping_options": [],
"subtotal_override": null,
"total_tax_override": null,
"payment_method_details": {},
"request_client_details": [],
"total_discount_override": null
},
"client_id": "b9ffc7ab-4eb9-4ec8-b300-0bc50b6ec894",
"reference": "",
"viewed_on": null,
"company_id": "492c861c-a64f-424f-8edf-9165667dc526",
"created_on": 1663327006,
"event_type": "purchase.created",
"updated_on": 1663327006,
"invoice_url": "https://gate.qorepay.com/p/d1b8e94e-6908-44e0-9724-2a3d6240bd0b/invoice/",
"checkout_url": "https://payments.qorepay.com/p/d1b8e94e-6908-44e0-9724-2a3d6240bd0b/",
"send_receipt": true,
"skip_capture": false,
"creator_agent": "",
"issuer_details": {
"website": "",
"brand_name": "ACME Insurance",
"legal_city": "",
"legal_name": "ACME Insurance",
"tax_number": "",
"bank_accounts": [
{
"bank_code": "",
"bank_account": ""
}
],
"legal_country": "ZA",
"legal_zip_code": "",
"registration_number": "",
"legal_street_address": ""
},
"marked_as_paid": false,
"status_history": [
{
"status": "created",
"timestamp": 1663327006
}
],
"cancel_redirect": "",
"created_from_ip": "102.65.48.194",
"direct_post_url": null,
"force_recurring": false,
"recurring_token": null,
"failure_redirect": "",
"success_callback": "",
"success_redirect": "",
"transaction_data": {
"flow": "payform",
"extra": {},
"country": "",
"attempts": [],
"payment_method": ""
},
"refundable_amount": 0,
"is_recurring_token": false,
"billing_template_id": "4af4427f-ec97-4830-bc3a-6fcb1e0ab69a",
"currency_conversion": null,
"reference_generated": "RV3",
"refund_availability": "none",
"payment_method_whitelist": null
}
Webhook authentication
Payloads are signed using asymmetric A.K.A. public-key cryptography to guarantee the authenticity of delivered callbacks. Each callback delivery request includes an X-Signature header field. This field contains a base64-encoded RSA PKCS#1 v1.5 signature of the SHA256 digest of the request body buffer.
You can obtain the public key for Webhook authentication fromWebhook.public_key
of the corresponding Webhook
You can obtain the public key for success callback authentication from Webhook public key.
Webhook callback payloads are signed using a dedicated key pair. You can obtain the public key from Webhook.public_key
.
Please note QorePay is not responsible for any financial losses incurred due to not implementing payload signature verification.
Creating a webhook
Webhooks
can be created via the QorePay dashboard or by API.
To create a webhook on the QorePay dashboard:
- Log in to your dashboard and click on Developers
- Navigate to Webhooks to create a new Webhook
- Remember to specify the eventsYou want to listen to
QorePay allows up to 8 webhooks shared between live and test modes.
đź‘Ť
Pro tip
When testing, you can get an instant webhook URL by visiting webhook.site. This will allow you to inspect the received payload without having to write any code or set up a server.