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

# Webhooks

# Payment Status Updates via Webhooks

Kashimi supports webhooks to notify you about unique status changes in a payment. Typically, you will receive a webhook when a payment is initiated (status `PENDING`), and when it is finalised (status `COMPLETED` or `FAILED`).

You can opt-in to receive webhooks per payment by including a `webhookUrl` in the payment metadata when [initiating a payment](/api-reference/endpoint/initiatePayments).

<Tip>
  **Prerequisites**

  Before you can receive webhooks, you must first register a webhook secret with Kashimi. This secret is used to generate HMAC-SHA256 signatures for each webhook payload to ensure authenticity.

  Contact the Kashimi team to register your webhook secret before implementing webhook endpoints.
</Tip>

## IP Allowlist

Kashimi sends webhooks from a fixed set of IP addresses. To ensure your endpoint only accepts legitimate webhook traffic, allowlist the following IPs in your firewall or infrastructure:

| Environment | IP Addresses                                                |
| ----------- | ----------------------------------------------------------- |
| Production  | Contact the Kashimi team for the current production IP list |
| Sandbox     | Contact the Kashimi team for the current sandbox IP list    |

<Info>
  Reach out to [admin@kashimi.tech](mailto:admin@kashimi.tech) to receive the up-to-date list of Kashimi webhook IP addresses for your environment.
</Info>

<AccordionGroup>
  <Accordion title="Secret Key Best Practices">
    Follow these security best practices for your webhook secret:

    **Randomness**

    * Use a cryptographically secure pseudo-random number generator
    * Avoid predictable values or custom algorithms
    * Generate truly random keys

    **Length**

    * Use at least 256 bits (32 bytes) for HMAC-SHA256
    * Keys shorter than the hash output length reduce security
    * Keys longer than the hash output don't significantly improve security

    **Key Management**

    * Refresh keys periodically to limit exposure damage
    * Rotate keys as part of your security practices
    * Store keys securely and never expose them in code or logs
  </Accordion>
</AccordionGroup>

## Webhook Payload

When a payment status changes, Kashimi sends a webhook with the following JSON payload:

```json theme={null}
{
  "eventName": "PAYMENT_STATUS_UPDATED",
  "eventId": "123e4567-e89b-12d3-a456-426614174000",
  "paymentId": "456e7890-e12b-34c5-d678-901234567890",
  "status": "COMPLETED",
  "timestamp": "2023-12-01T10:30:45.123Z"
}
```

### Payload Fields

| <div align="left">**Field**</div> | <div align="left">**Type**</div> | <div align="center">**Required**</div> | <div align="left">**Description**</div>                                 |
| --------------------------------- | -------------------------------- | -------------------------------------- | ----------------------------------------------------------------------- |
| `eventName`                       | string                           | <div align="center">✓</div>            | Always `"PAYMENT_STATUS_UPDATED"` for payment status webhooks           |
| `eventId`                         | string                           | <div align="center">✓</div>            | Unique identifier for this webhook event (UUID format)                  |
| `paymentId`                       | string                           | <div align="center">✓</div>            | The Kashimi payment ID that this status update relates to (UUID format) |
| `status`                          | string                           | <div align="center">✓</div>            | The new payment status: `PENDING`, `COMPLETED`, `FAILED`, or `UNKNOWN`  |
| `timestamp`                       | string                           | <div align="center">✓</div>            | ISO 8601 timestamp when the status change occurred                      |

<Info>
  The `eventId` is unique for each webhook delivery attempt, while `paymentId` identifies the specific payment. Use `paymentId` to correlate webhook events with payments in your system.
</Info>

# Webhook Security

To ensure webhook authenticity, Kashimi uses HMAC-SHA256 signatures. Each webhook payload is signed using your registered secret key.

<Info>
  **Why signatures are essential:**

  * **Authentication** - Verify the webhook actually came from Kashimi, not an attacker
  * **Integrity** - Ensure the payment status data hasn't been tampered with in transit
  * **Replay protection** - Prevent malicious actors from resending old webhook payloads
  * **Trust** - Safely process payment updates without risking fraudulent notifications
</Info>

<Warning>
  Without signature verification, anyone could send fake payment status updates to your webhook endpoint, potentially causing incorrect order fulfilment or financial discrepancies.
</Warning>

## Signature Validation

When you receive a webhook, validate it following these steps:

<Steps>
  <Step title="Extract the signature header">
    Extract the `X-Kashimi-Signature` header from the incoming request
  </Step>

  <Step title="Generate expected signature">
    Generate an HMAC-SHA256 signature using your secret key and the request body
  </Step>

  <Step title="Compare signatures">
    Compare the signatures - if they match, the webhook is authentic and untampered.
  </Step>
</Steps>

<br />

<br />

**TypeScript example**

```typescript theme={null}
import * as crypto from "node:crypto";

function validateWebhookSignature(input: {
  payload: string,
  receivedSignature: string,
  secret: string
}) {
  const { payload, receivedSignature, secret } = input;

  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  const isValid = crypto.timingSafeEqual(
    Buffer.from(expectedSignature, 'hex'),
    Buffer.from(receivedSignature, 'hex')
  );

  return isValid;
}

// Usage
const payload = JSON.stringify({"eventName":"PAYMENT_STATUS_UPDATED","eventId":"775ada63-568f-4d2c-8cf2-1401801d375b","paymentId":"a0f527ea-07d4-4d7e-a759-1098085ead7d","status":"FAILED","timestamp":"2025-07-15T09:19:59.701Z"});
const receivedSignature = '8f10cf71972a90dba50b206f3debe7681ae70da3f787643645e1f5c37c4a8bab'; // taken from the x-kashimi-signature header
const secret = 'your-secret-here';
const isSignatureValid = validateWebhookSignature({ payload, receivedSignature, secret });
console.log(isSignatureValid);
```

**Express.js middleware for webhook signature validation**

```typescript theme={null}
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-kashimi-signature'] as string;
  const payload = req.body.toString();

  if (!signature) {
    return res.status(400).json({ error: 'Missing signature header' });
  }

  const isValid = validateWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid webhook signature' });
  }

  res.status(200).json({ received: true });
});
```
