Webhooks: Asynchronous Payment Notifications
Webhooks allow you to receive real-time notifications whenever a payment status changes. Instead of continuously polling the API, your system will automatically be informed when an event occurs, making payment tracking more efficient.
When an event is triggered, Purse API will send a POST request to the webhook URL you configured.
Webhook Event Triggers
Whenever the field amounts undergoes a change, we initiate a notification to the webhook.url provided when you created your Client-Session.
Do not forget to specify the webhook.url parameter when creating the client session if you want to receive the notification
Notifications on Operation Status Changes
- By default, you will be notified of each statuses events occured on the whole payment
- If you want to subscribe on specific notifications, you shall value
webhook.event_typeswithin Create client-session or Create payment requests - For more information, please check Advanced webhooks subscriptions section
Changes in the status of operations trigger updates to the available or processed amounts (captured, refunded, voided). Each of these updates results in a notification being sent.
Examples:
- When a capture is initiated, the
available_to_captureamount decreases : Notification - If a capture transitions from
pendingtofailure, the amount becomes available to capture again (available_to_captureincreases) : Notification - If a capture transitions from
pendingtosuccess, thetotal_capturedamount increases : Notification
When a payment status transitions to Authorized, the total_authorized amount increases. Therefore, a notification is sent as well.
Webhook Payload Example
When a webhook is triggered, Purse API sends a JSON payload to your webhook URL. This notification includes a payment payload identical to the Get Payment endpoint, allowing the merchant to perform all his necessary actions :
Webhook Payload Example
{
"id": "9a14962c-bdcf-49d1-8673-e75dfb48013f",
"version": 7,
"created_at": "2020-08-25T10:42:59.123+02:00",
"entity_id": "89a237d0-20f9-438e-b46c-3993c46b7bbe",
"amount": 4299,
"currency": "EUR",
"client_session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"order_reference": "order_01",
"order": {
"reference": "123456789",
"net_amount": 12000,
"tax_amount": 2000,
"billing_address": {
"gender": "FEMALE",
"first_name": "Emily",
"middle_name": "Rose",
"last_name": "PARKER",
"address_lines": [
"NextNow",
"67 Rue de Luxembourg"
],
"city": "Lille",
"postal_code": "59777",
"country_code": "FR",
"province_code": "FR-HDF",
"phone_number": "+33111111111",
"mobile_phone_number": "+33222222222",
"work_phone_number": "+33444444444"
},
"shipments": [
{
"net_amount": 12000,
"delivery_type": "EXTERNAL_PICKUP",
"delivery_quickness": "REGULAR",
"delivery_method_reference": "#1123-pickup",
"estimated_delivery_date_time": "2023-10-30T15:40:00+02:00",
"shipping_address": {
"gender": "FEMALE",
"first_name": "Rodriguez",
"middle_name": "Eios",
"last_name": "DESANTONS",
"address_lines": [
"Av. de la roja 1676"
],
"city": "Porto",
"postal_code": "22000",
"country_code": "PT",
"province_code": "PT-13",
"phone_number": "+33555555555",
"mobile_phone_number": "+33666666666",
"work_phone_number": "+33777777777",
"delivery_point_name": "home"
},
"item_lines": [
{
"type": "PHYSICAL",
"sku_reference": "2600218",
"name": "Camiseta100 niño GYM",
"brand": "Quivio",
"unit_gross_price": 5000,
"net_amount": 12000,
"quantity": 2,
"tax_amount": 2000,
"tax_rate": 20,
"seller_reference": "#42-ACME",
"seller_name": "Acme Corp",
"is_marketplace_seller": true
},
{
"type": "SHIPPING_FEES",
"name": "Transporte",
"net_amount": 0,
"unit_gross_price": 0,
"quantity": 1,
"tax_amount": 0,
"tax_rate": 0
}
]
}
]
},
"capture_mode": "MANUAL",
"amounts": {
"authorize_pending": 0,
"total_authorized": 4299,
"total_voided": 0,
"available_to_void": 1299,
"total_captured": 3000,
"available_to_capture": 1299,
"total_refunded": 0,
"available_to_refund": 3000
},
"overview": {
"consumed": "PARTIALLY",
"captured": "PARTIALLY",
"voided": "NOT",
"refunded": "NOT"
},
"split": [
{
"partner": "easy2play",
"method": "giftcard",
"amount": 2000
},
{
"partner": "dalenys",
"method": "creditcard",
"amount": 2299
}
],
"authorization": {
"status": "AUTHORIZED",
"partner_transactions": [
{
"id": "1a14962c-bdcf-49d1-8673-e75dfb48013f",
"created_at": "2020-08-25T10:42:59.123+02:00",
"updated_at": "2020-08-25T10:42:59.123+02:00",
"status": "AUTHORIZED",
"amount": 2000,
"partner": "easy2play",
"method": "giftcard",
"partner_reference": "6898f79aa20af",
"partner_status": "1",
"partner_status_description": "Successful operation"
},
{
"id": "e3a6516b-0ec4-4a5b-92b0-7bb9693e82c1",
"created_at": "2020-08-25T10:42:59.123+02:00",
"updated_at": "2020-08-25T10:42:59.123+02:00",
"status": "AUTHORIZED",
"amount": 2299,
"partner": "dalenys",
"method": "creditcard",
"card": {
"bin": "513842",
"last_four_digits": "1234",
"holder_name": "John Doe",
"expiry_month": "01",
"expiry_year": "24",
"selected_network": "VISA",
"three_ds": {
"eci": "01"
}
},
"partner_reference": "A4232036332",
"partner_status": "0000",
"partner_status_description": "Successful operation"
}
]
},
"captures": [
{
"id": "1a14962c-bdcf-49d1-8673-e75dfb48013f",
"amount": 3000,
"created_at": "2020-08-25T10:42:59.123+02:00",
"updated_at": "2020-08-25T10:42:59.123+02:00",
"triggered_by": "MERCHANT",
"merchant_reference": "CAP#1223445",
"status": "SUCCESS",
"partner_transactions": [
{
"id": "4a14962c-bdcf-49d1-8673-e75dfb48013f",
"created_at": "2020-08-25T10:42:59.123+02:00",
"updated_at": "2020-08-25T10:42:59.123+02:00",
"status": "SUCCESS",
"amount": 2000,
"partner": "easy2play",
"method": "giftcard",
"partner_reference": "9898f79aa20rt",
"partner_status": "1",
"partner_status_description": "Successful operation"
},
{
"id": "28267b24-4def-4440-b7a4-5ee0f145c56d",
"created_at": "2020-08-25T10:42:59.123+02:00",
"updated_at": "2020-08-25T10:42:59.123+02:00",
"status": "SUCCESS",
"amount": 1000,
"partner": "dalenys",
"method": "creditcard",
"partner_reference": "C5232036332",
"partner_status": "0000",
"partner_status_description": "Successful operation"
}
]
}
],
"voids": [],
"refunds": [],
"links": {
"self": {
"href": "/v2/payment/9a14962c-bdcf-49d1-8673-e75dfb48013f",
"method": "GET"
},
"capture": {
"href": "/v2/payments/9a14962c-bdcf-49d1-8673-e75dfb48013f/captures",
"method": "POST"
},
"refund": {
"href": "/v2/payments/9a14962c-bdcf-49d1-8673-e75dfb48013f/refunds",
"method": "POST"
},
"void": {
"href": "/v2/payments/9a14962c-bdcf-49d1-8673-e75dfb48013f/voids",
"method": "POST"
}
}
}
Webhook Security: Verifying Data Integrity
To ensure the authenticity and integrity of webhook notifications, Purse API cryptographically signs all webhook payloads using JSON Web Signature (JWS). (It uses the same technology as purse-redirection data)
How to Securely Validate Webhooks?
1️⃣ Retrieve the public key from the signing endpoint:
- Endpoint:
/payment/v2/signing-jwks - Method:
GET - Signing Key API Reference
2️⃣ Use a JWT/JWS library (e.g., Java, JavaScript, PHP) to decode the payload.
3️⃣ Verify the signature using the retrieved public key.
4️⃣ Ensure that the payload has not been modified before processing the event.
Example: Verifying Webhooks with jwt.io
- Copy the webhook payload into the "Encoded" field.
- Paste the public key retrieved from
/signing-jwksinto the "Public Key" field. - If the signature is valid, process the webhook event safely.
- Always verify both the source and signature of webhook events.
- Use HTTPS endpoints to securely receive webhook notifications.
- Implement logging and monitoring to detect unexpected failures or delays.
Handling Webhook Failures & Delays
Webhooks are designed to provide real-time updates, but in case of network issues or processing failures, it’s important to have fallback mechanisms in place.
Retry Logic
If your server fails to process a webhook, respond with a 500 HTTP status. Purse API will automatically retry sending the webhook at increasing intervals.
The notification mechanism should call the webhook.url every time a status changes in the payment resource.
However, we have no confirmation from the merchant that the notification has successfully been received. Therefore,
we recommend to call back our API sometimes when a PENDING status has not been notified in a while.
Manually Retrieve Payment Status
In order to prevent the overload of the merchant system with webhook requests, we suggest publishing payment status changes into your own messaging system (RabbitMQ, Apache Kafka, Google Pub/Sub, ...). This approach allows the merchant to asynchronously handle payment updates, ensuring efficient processing when statuses change.
If a webhook is not received within the expected timeframe, you can manually check the payment status via the API:
Endpoint: /payment/v2/payments/{{payment-id}}
- Method:
GET - Get Payment API Reference
Best Practices for Webhook Handling
- Implement retry logic to handle temporary failures.
- Store a history of webhook events to maintain data consistency.
- Use message queuing systems (e.g., RabbitMQ, Kafka, Google Pub/Sub) to asynchronously process webhooks.
By implementing webhooks, you ensure that your payment system is always up-to-date with real-time transaction changes, improving automation and user experience.