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:
| Field | Description |
|---|---|
| URL | Your HTTPS endpoint that will receive POST requests (e.g., https://api.yourserver.com/kda-webhook) |
| Events | Subscribe 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:
| Action | Description |
|---|---|
| Toggle active/inactive | Temporarily disable without deleting |
| Test | Send a test payload — choose Success or Failure from the dropdown to verify your endpoint handles both events correctly |
| Rotate secret | Generate a new signing secret — update your server immediately after |
| Delete | Permanently 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.