> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paycashless.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

Webhooks (or callbacks) let you automatically receive asynchronous events from Paycashless when important events happen — like when a payment status changes or a virtual account gets credited.

## Register your webhook URLs

Webhooks are currently registered when you call APIs via the `callbackUrl` field.

## Webhook Structure

All webhooks have the same payload structure.

* `event` : the name of the event
* `data` : event data specific to the event being sent

## Webhook Retries

Always acknowledge a webhook instantly by responding with `2xx`, else it will be considered as failed. Failed webhooks are retried with constant backoff for a maximum of 3 times with a delay of 1 minute.

## Webhook Security

To ensure you are receiving webhooks from Paycashless we provide `Request-Signature` and `Request-Timestamp` in the webhook request header.
This is similar to the API signing request, but in reverse. You MUST validate the signature to ensure it's originating from Paycashless.

Webhook signature is signed using HMAC SHA-512 with your API secret as the signing key. `Request-Signature` and `Request-Timestamp` will be provided in the webhook request header for you to reconstruct the message for verification.

To verify the signature follow the process below:

| Step | Action      | Description                                                                                                                                                                                                                                                 |
| ---- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1    | Hash        | Take the event data object, stringify the object and hash it with HMAC SHA-512 algorithm using your API secret as the signing key (output should be hex-encoded).                                                                                           |
| 2    | Concatenate | Concatenate your full `callback url` exactly as you provided it, `hashed event data`, and `Request-Timestamp` from the header. There are no spaces or other characters between these values. The order of the fields must follow the order stipulated here. |
| 3    | Sign        | Take the string from the Concatenate step and generate a HMAC SHA-512 signature using your API secret as the signing key.                                                                                                                                   |
| 4    | Encode      | Take the output of the Sign step and hex-encode it.                                                                                                                                                                                                         |
| 5    | Verify      | Compare the recreated signature with the content of `Request-Signature` header.                                                                                                                                                                             |

The fields used to generate the signature are as follows. If the conditions below are not met, you won't be able to recreate the `Request-Signature` header.

| Field        | Description                                                                                                                               |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Callback URL | Lowercased full URL as provided with the base url and search parameters (e.g. `https://yourwebsite.com/callback/paycashless?notify=all`). |
| Hashed Body  | The event data object, `stringified` and hashed using `HMAC SHA-512` algorithm.                                                           |
| Timestamp    | The value gotten from `Request-Timestamp` header.                                                                                         |

Study the node.js webhook verification example code below:

```js verify_webhook_signature.js theme={null}
import { createHmac, timingSafeEqual } from 'crypto';

function sha512Sign(message, secret) {
  return createHmac('sha512', secret).update(message).digest('hex');
}

export function sha512Verify(data: string, signature: string, key: string): boolean {
  // Generate the expected HMAC
  const expectedSignature = createHmac('sha512', key)
    .update(data, 'utf8')
    .digest('hex');
  
  // Convert both signatures to buffers for timing-safe comparison
  const expectedBuffer = Buffer.from(expectedSignature, 'hex');
  const providedBuffer = Buffer.from(signature, 'hex');
  
  // Ensure both buffers are the same length before comparison
  if (expectedBuffer.length !== providedBuffer.length) {
    return false;
  }
  
  // Use timing-safe comparison to prevent timing attacks
  return timingSafeEqual(expectedBuffer, providedBuffer);
}

// Get the timestamp from 'Request-Timestamp' header
const requestTimestamp = "1704931925543";
const callbackUrl = "https://webhook.site/e1ae9397-bd8d-4327-ad92-da002ea2ef08";
const API_SECRET = "YOUR_API_SECRET"; // replace these with your actual API secret value 
const requestSignature = "f17e737bf9d4a1f46f130126be4b2cdb3109a347565b5f824f9da0a45f14663db899812654d849d731cf825b884e1363a8364c5e622ec86f338a31fdeec7812d";
const event = {"event":"events.payout.succeeded","data":{"amount":10000,"callbackUrl":"https://webhook.site/e1ae9397-bd8d-4327-ad92-da002ea2ef08","confirmationState":"confirmed","currency":"NGN","destinationAccountNumber":"9845648577","destinationBankId":"bank_538ed2056326432ba8e6853b613997bb","emtl":0,"failureCode":null,"failureMessage":null,"fee":1500,"id":"po_dtb9z9jk4fs6vqelh3hb8dxcyscnldpx","liveMode":true,"metadata":{"category":"transfer"},"narration":"zapped","networkSessionId":"090286250606000850470781628636","reference":"trx_fww7b31pbs5mmT3k3qfb47","requiresConfirmation":false,"status":"succeeded","vat":0}}

const bodyHash = sha512Sign(JSON.stringify(event.data), API_SECRET);
const stringToSign = `${callbackUrl}${bodyHash}${timestamp}`;

// Generate the Request-Signature value for the header
const isValid = sha512Verify(stringToSign, requestSignature, API_SECRET);

console.log(isValid ? "Signature is valid" : "Signature is not valid");
```
