Skip to main content

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.

Webhook Configuration

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

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_capture amount decreases : Notification
  • If a capture transitions from pending to failure, the amount becomes available to capture again (available_to_capture increases) : Notification
  • If a capture transitions from pending to success, the total_captured amount increases : Notification
Getting Authorized status

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?

Webhook Verification Steps

1️⃣ Retrieve the public key from the signing endpoint:

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-jwks into the "Public Key" field.
  • If the signature is valid, process the webhook event safely.
Security Best Practices
  • 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.

warning

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

warning

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: Get Payment Request

Endpoint: /payment/v2/payments/{{payment-id}}

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.