Docs
Webhooks
How to Set Up Webhooks

How to Set Up Webhooks

This guide outlines how to configure and verify webhooks.

Identify the events to monitor

Identify the specific events and their associated data objects your webhook endpoint will need to interpret.

Create a webhook endpoint

Set up an HTTP endpoint on your local server capable of handling unauthenticated POST requests. You can utilize tools like ngrok (opens in a new tab) to make your local server accessible online for testing purposes.

Configure webhook endpoints

To configure webhooks through the Partner Dashboard:

  1. Navigate to Developers > Webhooks and click the Create Endpoint button
  2. Enter a webhook description and URL
  3. Select which events the webhook should trigger; specifying * will enable all events
  4. Click the Create button

Webhook endpoints can also be configured using the Webhook EndpointsAPI.

Handle requests from

Your endpoint must be configured to read event objects for the type of event notifications you want to receive. sends events to your webhook endpoint as part of a POST request with a JSON payload.

  • Inspect event objects: Each event is delivered as an object containing an id, account_id, type and related resource information within a nested data structure. Ensure your endpoint verifies the event type and decodes the payload accordingly.
  • Acknowledge receipt: Immediately return a 2xx series response to confirm receipt of each event, before any complex processing that might delay the response. For instance, send a 200 response before marking an order as paid in your system.
  • Understand built in retries: automatically retries sending webhook events for non-2xx responses. Events not acknowledged with a 2xx response are considered failed, but you can query missed events to update your records accordingly.

Check webhook signatures

Secure your webhook data by verifying signatures provided in the request headers. This step confirms the authenticity of the webhook requests, ensuring they are indeed from and not a third-party impersonating .

1. Get the webhook endpoint’s secret: You can get this secret key from the Dashboard or API when you first configure the endpoint.

2. Get the header values from the POST: employs SHA-256 hash-based message authentication codes (HMAC) for this purpose, with a focus on the "v1" signature scheme as the only valid method for live signatures. Each webhook sent by is signed, including a UTC timestamp and one or more signatures in the payments-signature header.

let {{ACCOUNT_HEADER}}Signature = "t=1614049713663,v1=8981f5902896f479fa9079eec71fca01e9a065c5b59a96b221544023ce994b02"
const details = {{ACCOUNT_HEADER}}Signature.split(',').reduce(
 (accum, item) => {
  const kv = item.split('=');
  if (kv[0] === 't') {
   accum.timestamp = kv[1];
  }
  if (kv[0] === scheme) {
   accum.signature = kv[1];
  }
  return accum;
 },
 {
  timestamp: -1,
  signature: -1,
 }
);

3. Prepare the signed payload: Combine the timestamp (as a string), a period (.), and the actual JSON payload from the request body to create the signed payload string.

let payload = details.timestamp + "." + JSON.stringify(webhookBody);

4. Identify the expected signature: Use the SHA256 hash function to create an HMAC. The signing secret of the endpoint is your key, and the signed_payload string is your message.

let expectedSignature = CryptoJS.HmacSHA256(payload, private_key).toString(CryptoJS.enc.Hex);

5. Compare the signatures: Match the expected signature against the one(s) provided in the header. To validate the timing, check the difference between the current timestamp and the received timestamp, ensuring it's within your acceptable range. For security against timing attacks, employ a constant-time string comparison method to compare the expected signature with each received signature.

if (expectedSignature === details.signature) {
 console.log("Signature match with {{ACCOUNT_HEADER}}_signature");
}

Refer to the following example:

// Can be found in the webhook payload header
let {{ACCOUNT_HEADER}}_signature = "t=1614049713663,v1=8981f5902896f479fa9079eec71fca01e9a065c5b59a96b221544023ce994b02"
// Can be found in the payload
let body = JSON.stringify(webhook.body);

const details = parseHeader({{ACCOUNT_HEADER}}_signature, "v1");

console.log(details);

if (!details || details.timestamp === -1) {
 try {
  throw new Error("Unable to extract timestamp and signature from header")
  console.log("Unable to extract timestamp and signature from header")
 } catch (e) {
  console.log(e.name, e.message);
 }
}

if (!details || details.signature === -1) {
 try {
  throw new Error("No signature found with expected scheme")
  console.log("No signature found with expected scheme")
 } catch (e) {
  console.log(e.name, e.message);
 }
}

let payload = details.timestamp + "." + body;
let expectedSignature = CryptoJS.HmacSHA256(payload, private_key).toString(CryptoJS.enc.Hex);

console.log(expectedSignature);

if(expectedSignature == details.signature) {
 console.log("Signature match with {{ACCOUNT_HEADER}}_signature");
}
  
function parseHeader(header, scheme) {
 if (typeof header !== 'string') {
  return null;
 }

 return header.split(',').reduce(
  (accum, item) => {
   const kv = item.split('=');

   if (kv[0] === 't') {
    accum.timestamp = kv[1];
   }

   if (kv[0] === scheme) {
    accum.signature = kv[1];
   }

   return accum;
  },
  {
   timestamp: -1,
   signature: -1,
  }
 );
}