Webhooks let you receive HTTP callbacks when events occur in your partner account — such as card transactions, KYC status changes, or balance updates.
Get current config
curl https://api.contro.me/v1/partner/webhooks/config \
-H "x-contro-api-key: $CONTRO_API_KEY"
Response:
{
"webhookUrl": "https://your-app.com/webhooks/contro",
"webhookSecret": "whsec_...",
"subscribedEvents": ["card.transaction", "cardholder.kyc.updated"]
}
Update config
curl -X PATCH https://api.contro.me/v1/partner/webhooks/config \
-H "x-contro-api-key: $CONTRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://your-app.com/webhooks/contro",
"webhookSecret": "whsec_your_secret_min_16_chars",
"subscribedEvents": ["card.transaction", "cardholder.kyc.updated", "balance.low"]
}'
| Field | Type | Description |
|---|
webhookUrl | string | HTTPS URL to receive events. Must use https://. Example: "https://your-app.com/webhooks/contro" |
webhookSecret | string | Secret for HMAC-SHA256 signature verification. Min 16 characters. Example: "whsec_your_secret_min_16_chars" |
subscribedEvents | string[] | Event types to subscribe to. Example: ["card.transaction", "cardholder.kyc.updated"] |
Event types
| Event | Description |
|---|
card.transaction | A card transaction occurred |
card.status.changed | A card was activated, frozen, unfrozen, or cancelled |
cardholder.kyc.updated | KYC status changed (approved, rejected) |
cardholder.created | A new cardholder was created |
balance.low | Balance fell below threshold |
balance.top_up | Balance was topped up |
Verifying webhook signatures
Every webhook request includes an HMAC-SHA256 signature in the X-Contro-Signature header. The signature format is t={timestamp},v1={hmac}, where the HMAC is computed over {timestamp}.{body}.
import crypto from "node:crypto";
function verifyWebhookSignature(body, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("=", 2))
);
const timestamp = parts.t;
const receivedHmac = parts.v1;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${body}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(receivedHmac),
Buffer.from(expected)
);
}
// In your webhook handler:
app.post("/webhooks/contro", (req, res) => {
const body = JSON.stringify(req.body);
const signature = req.headers["x-contro-signature"];
if (!verifyWebhookSignature(body, signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Process the event — event type is in the X-Contro-Event header
const eventType = req.headers["x-contro-event"];
console.log("Event type:", eventType);
res.status(200).send("OK");
});
Always verify webhook signatures before processing events. Unverified webhooks could be spoofed by attackers.
Retry policy
Failed deliveries (non-2xx responses or timeouts) are retried with exponential backoff:
| Attempt | Delay |
|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 24 hours |
After 5 failed retries, the event is marked as failed. You can manually retry failed events.
List webhook events
View the delivery history for your webhooks:
curl "https://api.contro.me/v1/partner/webhooks/events?limit=20&status=failed" \
-H "x-contro-api-key: $CONTRO_API_KEY"
Query parameters
| Parameter | Type | Description |
|---|
cursor | string | Pagination cursor from the previous response’s nextCursor |
limit | integer | Items per page (1–100, default 20). Example: 50 |
status | string | Filter by delivery status. One of pending, delivered, failed |
eventType | string | Filter by event type. Example: "card.transaction" |
Event fields
| Field | Type | Description |
|---|
id | string | Event ID. Example: "evt_abc123" |
eventType | string | Event type. One of card.transaction, card.status.changed, cardholder.kyc.updated, cardholder.created, balance.low, balance.top_up |
status | string | Delivery status. One of pending, delivered, failed |
attemptCount | number | Number of delivery attempts made. Example: 3 |
lastAttemptAt | string | null | ISO 8601 timestamp of last delivery attempt, or null. Example: "2026-03-20T14:30:00Z" |
nextRetryAt | string | null | ISO 8601 timestamp of the next scheduled retry, or null if no retry is pending. Example: "2026-03-20T15:00:00Z" |
lastResponseStatus | number | null | HTTP status code from the last delivery attempt, or null. Example: 500 |
createdAt | string | ISO 8601 event creation timestamp. Example: "2026-03-20T14:30:00Z" |
Retry a failed event
Manually retry delivery of a failed event:
curl -X POST https://api.contro.me/v1/partner/webhooks/events/{eventId}/retry \
-H "x-contro-api-key: $CONTRO_API_KEY"