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

# Webhook Notifications

> Receive real-time notifications about transaction status updates and other events.

Kotani Pay sends webhook notifications to inform your application about key lifecycle events—transaction status changes, payment confirmations, compliance updates, and scheduled system announcements. Use these callbacks to drive your automations instead of polling for status.

## Supported Events

Configure one webhook endpoint per environment from the dashboard and opt in to any combination of events:

* **Transaction Status Updates** – Deposit, withdrawal, or transfer status changes (e.g., `PENDING` → `COMPLETED`)
* **Payment Confirmations** – Real-time acknowledgements when a payment settles on- or off-chain
* **KYC Status Changes** – Triggers when a customer's verification outcome changes
* **System Events** – Low-volume operational notices and maintenance alerts

### Event Types

The following event types are available:

**Transaction Events:**

* `transaction.deposit.status.updated` - Deposit transaction status changed
* `transaction.withdrawal.status.updated` - Withdrawal transaction status changed
* `transaction.onramp.status.updated` - On-ramp transaction status changed
* `transaction.offramp.status.updated` - Off-ramp transaction status changed
* `transaction.status.updated` - *(Deprecated)* Generic transaction status update

**Payment Events:**

* `payment.confirmed` - Payment has been confirmed

**KYC Events:**

* `kyc.status.changed` - Customer verification status changed

**System Events:**

* `system.event` - Operational notices and maintenance alerts

## Payload Format

Webhooks are sent as HTTP `POST` requests with a JSON payload. The request body includes the event name, data payload, and a convenience copy of the signature. The canonical signature is delivered in the `X-Kotani-Signature` header.

```json theme={null}
{
  "event": "transaction.deposit.status.updated",
  "data": {
    "referenceId": "ABC123",
    "status": "SUCCESSFUL",
    "timestamp": "2025-01-01T00:00:00Z"
  },
  "signature": "sha256=abc123..."
}
```

> **Tip:** The payload's `signature` field is provided for quick sanity checks, but the header is the source of truth for verification.

## Verifying Signatures

Every payload is signed with your dashboard-configured webhook secret. Validate the signature before acting on the event:

1. Parse the JSON payload from the request body.
2. **Remove the `signature` field** from the parsed payload.
3. Compute an HMAC-SHA256 digest of the remaining payload: `sha256=HMAC(secret, JSON.stringify(payloadWithoutSignature))`.
4. Compare the digest with the `X-Kotani-Signature` header using a timing-safe comparison.

> **Important:** The signature is computed from only the `event` and `data` fields. You must exclude the `signature` field itself when verifying.

### Node.js helper

```ts theme={null}
import crypto from "crypto";

export function verifyWebhook({
  secret,
  payload,
  headerSignature,
}: {
  secret: string;
  payload: { event: string; data: Record<string, any>; signature?: string };
  headerSignature: string;
}) {
  // Remove the signature field from the payload
  const { signature, ...payloadWithoutSignature } = payload;

  // Compute HMAC-SHA256 from the payload without signature
  const payloadString = JSON.stringify(payloadWithoutSignature);
  const computed =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(payloadString).digest("hex");

  try {
    return crypto.timingSafeEqual(
      Buffer.from(computed),
      Buffer.from(headerSignature.trim())
    );
  } catch {
    return false;
  }
}
```

### Example usage

```ts theme={null}
import express from "express";

const app = express();

app.post("/webhook", express.json(), (req, res) => {
  const headerSignature = req.headers["x-kotani-signature"] as string;
  const webhookSecret = process.env.KOTANI_WEBHOOK_SECRET;

  const isValid = verifyWebhook({
    secret: webhookSecret,
    payload: req.body,
    headerSignature,
  });

  if (!isValid) {
    return res.status(401).send("Invalid signature");
  }

  // Process the webhook event
  console.log("Event:", req.body.event);
  console.log("Data:", req.body.data);

  res.status(200).send("OK");
});
```

## Configuring Webhooks

1. Log into the Kotani Pay dashboard.
2. Navigate to **Settings → Webhooks**.
3. Provide a publicly reachable HTTPS URL.
4. Select the events you want to subscribe to.
5. Copy or generate a signing secret and store it securely (e.g., an environment variable).
6. Save your changes.

You can rotate the secret at any time; remember to update your verification logic before applying the new secret in production.
