Skip to main content

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.
PrerequisitesBefore 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.

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:
EnvironmentIP Addresses
ProductionContact the Kashimi team for the current production IP list
SandboxContact the Kashimi team for the current sandbox IP list
Reach out to [email protected] to receive the up-to-date list of Kashimi webhook IP addresses for your environment.
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

Webhook Payload

When a payment status changes, Kashimi sends a webhook with the following JSON payload:
{
  "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

Field
Type
Required
Description
eventNamestring
Always "PAYMENT_STATUS_UPDATED" for payment status webhooks
eventIdstring
Unique identifier for this webhook event (UUID format)
paymentIdstring
The Kashimi payment ID that this status update relates to (UUID format)
statusstring
The new payment status: PENDING, COMPLETED, FAILED, or UNKNOWN
timestampstring
ISO 8601 timestamp when the status change occurred
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.

Webhook Security

To ensure webhook authenticity, Kashimi uses HMAC-SHA256 signatures. Each webhook payload is signed using your registered secret key.
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
Without signature verification, anyone could send fake payment status updates to your webhook endpoint, potentially causing incorrect order fulfilment or financial discrepancies.

Signature Validation

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

Extract the signature header

Extract the X-Kashimi-Signature header from the incoming request
2

Generate expected signature

Generate an HMAC-SHA256 signature using your secret key and the request body
3

Compare signatures

Compare the signatures - if they match, the webhook is authentic and untampered.


TypeScript example
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
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 });
});