Skip to main content
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., PENDINGCOMPLETED)
  • 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.
{
  "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

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

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.