Overview

When building integrations with Payable, you may want your applications to receive event notifications as they happen, to inform downstream actions in your own systems.

To enable webhook events, you first need to register webhook endpoints, which will receive event data.

To get set up, share your endpoints with us and we will register them for you.

Examples of when you may want to receive a webhook are; when a payment is successfully processed by your bank, or a customer pays an invoice.

Webhook signatures

A webhook signature is a security measure which allows you to verify that it is Payable who is sending the webhook. To authenticate the integrity of a webhook, each webhook contains a hash-based message authentication code (HMAC) in its webhook-signature header.
Payable can share the signing secret unique to your webhook to verify webhook's signature.
In order to generate the signature you will need to append the request body with the webhook-idand webhook-timestamp headers.
Make sure the object that you’re using to decode the body of a webhook is UTF-8 compliant and doesn’t change the content in any way (ex: not adding any formatting)

Here is an example on how you might verify the webhook-signature in javascript:

var signatureHeader = "v1,/BkkLCKduywdWKpRuJARaYkLB0M12m4C9c2bJfTsIc0="; // "webhook-signature" header
var id = "msg_2dabe5KfiXL4CUSBwdoRxUJK4X1"; // "webhook-id" header
var timestamp = "1709565206"; // "webhook-timestamp" header

const body = '{}'; // webhook payload, make sure not to alter is from the origin request

const crypto = require('crypto');

signedContent = `${id}.${timestamp}.${body}`;
const secret = "YOUR_SECRET"; // this is your webhook secret

const secretBytes = new Buffer(secret.split('_')[1], "base64");
const signature = crypto
  .createHmac('sha256', secretBytes)
  .update(signedContent)
  .digest('base64');

console.log(signature);

const result = isSignatureContained(signatureHeader, signature);
console.log(`Signature match result: ${result}`)

function isSignatureContained(signatureHeader, calculatedSignature) {
    const signatures = signatureHeader.split(" ");

    for (const signature of signatures) {
        const [version, value] = signature.split(",");

        if (calculatedSignature === value) {
            return true;
        }
    }
    
    return false;
}

Technical Details

Payable notifications depend on the events available. For each event, Payable will send a POSTrequest to your endpoint in a JSON format.

To acknowledge receipt of an event, your endpoint must respond with a 2xx HTTP status code to Payable within 5 seconds. If the endpoint takes longer to respond or returns an HTTP status code different from 2xx, the webhook will be re-sent at a later time using an exponential backoff strategy.

Webhook idempotency

Each webhook has an idempotency_key field . This is passed through as the HTTP body. You can save these IDs as you process webhooks to ensure each webhook is only processed once. If a webhook is sent multiple times, its idempotency_key will remain the same between requests.

Data Modeling

Payable Webhooks are structured with Categories, Events, and Data passed through the HTTP body.

  • category Describes which category the event belongs to; this will be the product you are using payment_orders.
  • type Specifies what happened to the object.
  • data Contains the updated object that changed.
{
  "category": "payment_orders",
  "data": {
     // webhook event
  },
  "id": "whk_bzzdrjgqd4nsr3o67muq3b6pje",
  "idempotency_key": "3111719b-75ea-4023-a0cd-8bf7609a7882",
  "organization_id": "org_bhdix4an76qqjzql7lfi7uaaaa",
  "request_id": "Root=1-65e9f52c-1302759c2b84fa3e3ccee2e4",
  "timestamp": "2024-03-01T10:00:00.000Z",
  "type": "payment_order_approval_required"
}


CategoryDescription
payment_ordersAny payment order lifecycle event.

Please see our Payment Orders Webhooks.