KDA API Logo
API Docs
Guide

Webhooks

Receive real-time notifications when purchases complete or fail.

Webhooks allow your server to receive real-time POST requests whenever a purchase transaction succeeds or fails. This eliminates the need to poll the API for transaction status.

Setting Up

Create Endpoint

Go to Dashboard → Settings → Dev Console and scroll to the Webhook Endpoints section.

Click Add Endpoint and enter:

FieldDescription
URLYour HTTPS endpoint that will receive POST requests (e.g., https://api.yourserver.com/kda-webhook)
EventsSubscribe to Purchase Success, Purchase Failed, or both

A signing secret is auto-generated for each endpoint. Copy it — you'll need it to verify incoming payloads.

Verify Signatures

Every webhook payload includes a signature field. Compute the same HMAC-SHA256 on your end and compare:

import { createHmac, timingSafeEqual } from "crypto";

function verifyWebhook(payload, secret) {
  // Destructuring creates a new object (non-mutating)
  const { signature, ...rest } = payload;
  const body = JSON.stringify(rest);
  const expected = createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  
  // timingSafeEqual throws if lengths mismatch
  if (expected.length !== signature.length) {
    return false;
  }

  return timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}
import hmac, hashlib, json

def verify_webhook(payload: dict, secret: str) -> bool:
    # Copy to avoid mutating the original dictionary
    payload_copy = payload.copy()
    signature = payload_copy.pop("signature", "")
    
    body = json.dumps(payload_copy, separators=(",", ":"))
    expected = hmac.new(
        secret.encode(), body.encode(), hashlib.sha256
      ).hexdigest()
      
    return hmac.compare_digest(expected, signature)
function verifyWebhook(array $payload, string $secret): bool {
    // PHP arrays are passed by value, so unset is safe here
    $signature = $payload['signature'] ?? '';
    unset($payload['signature']);
    
    // JSON_UNESCAPED_SLASHES is required to match JS/Python encoding
    $body = json_encode($payload, JSON_UNESCAPED_SLASHES);
    $expected = hash_hmac('sha256', $body, $secret);
    
    return hash_equals($expected, $signature);
}

The payload is signed before the signature field is added. When verifying, remove the signature field from the object, serialize it as JSON, and compute the HMAC on that string.

Signature verification is optional but strongly recommended to verify the integrity and origin of incoming webhooks.

You can test your endpoint with both success and failure payloads from the Developer Settings page. The dropdown next to the test button lets you choose which event to simulate.

Payload Format

Purchase Success

{
  "event": "PURCHASE_SUCCESS",
  "timestamp": "2026-06-22T19:30:00.000Z",
  "data": {
    "transactionId": "KDS-DATA-...",
    "type": "DATA",
    "amount": 500,
    "status": "SUCCESS",
    "providerRef": "KDS-REF-001",
    "identifier": "08031234567"
  },
  "signature": "a1b2c3d4e5f6..."
}

Purchase Failed

{
  "event": "PURCHASE_FAILED",
  "timestamp": "2026-06-22T19:30:05.000Z",
  "data": {
    "transactionId": "cm7def456...",
    "type": "AIRTIME",
    "amount": 200,
    "status": "FAILED",
    "errorMessage": "Insufficient provider balance",
    "identifier": "08031234567"
  },
  "signature": "f6e5d4c3b2a1..."
}

Receiving a Webhook

Your endpoint must:

Respond Quickly

Respond with 200 OK within 10 seconds. KDA Turbo will time out and log a warning if your server is slow.

Verify the Signature

Use the secret copied at creation time to verify every incoming payload.

(Optional) Prevent Replay Attacks

Reject payloads with timestamps older than 5 minutes.

If your endpoint responds with a non-200 status or times out, KDA Turbo logs the failure but does not currently retry. A retry queue is planned for a future release.

Managing Endpoints

From the Developer Settings page you can:

ActionDescription
Toggle active/inactiveTemporarily disable without deleting
TestSend a test payload — choose Success or Failure from the dropdown to verify your endpoint handles both events correctly
Rotate secretGenerate a new signing secret — update your server immediately after
DeletePermanently remove the endpoint

Security

  • HTTPS is required — webhook URLs must start with https://.
  • Signing secrets are per-endpoint and independent of your API keys. Rotating a webhook secret does not affect your API access.
  • API keys are never included in webhook payloads.