Paying too much to process cards? Get a cheaper quote today.Schedule a call →

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 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 Paydude

Your endpoint must be configured to read event objects for the type of event notifications you want to receive. Paydude 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 200 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. Any other response code is treated as a failure.
  • Understand built-in retries: Paydude automatically retries sending webhook events that do not receive a 200 response, up to 8 times over approximately 60 hours. See the retry schedule for full details, and use the delivery_status filter on the List Events endpoint to query missed events and 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 Paydude and not a third-party impersonating Paydude.

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: Paydude 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 Paydude is signed, including a UTC timestamp and one or more signatures (the t= value is in milliseconds — thirteen digits — not seconds, so compare it against Date.now() directly; a seconds-based replay window ported from another processor is off by a factor of 1000, which either rejects every request or never expires one) (the t= value is in milliseconds — thirteen digits — not seconds, so compare it against Date.now() directly; a seconds-based replay window ported from another processor is off by a factor of 1000, which either rejects every request or never expires one) in the payments-signature header.

let paymentsSignature = "t=1614049713663,v1=8981f5902896f479fa9079eec71fca01e9a065c5b59a96b221544023ce994b02"
const details = paymentsSignature.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. Use the raw request body — the exact bytes received — never a parsed object re-serialised with JSON.stringify. The signature was computed over those bytes, and a re-serialised object differs whenever key order, unicode escaping or number formatting does, so verification fails intermittently rather than consistently. Most frameworks parse JSON before your handler runs and you must opt out: in Next.js route handlers use await request.text() before any .json() call; in Express mount express.raw({ type: 'application/json' }) on the webhook route. Parse the body only after the signature checks out.

let payload = details.timestamp + "." + rawBody;

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 — the sample below contradicts this by using ===, which returns as soon as two characters differ and so leaks the position of the mismatch. Use crypto.timingSafeEqual on equal-length buffers (Node), after confirming the lengths match to compare the expected signature with each received signature.

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

Refer to the following example:

// Can be found in the webhook payload header
let payments_signature = "t=1614049713663,v1=8981f5902896f479fa9079eec71fca01e9a065c5b59a96b221544023ce994b02"
// Can be found in the payload
let body = rawBody;

const details = parseHeader(payments_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 payments_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,
  }
 );
}