Skip to main content
Webhooks let you receive HTTP callbacks when events occur in your partner account — such as card transactions, KYC status changes, or balance updates.

Configure webhooks

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"]
  }'
FieldTypeDescription
webhookUrlstringHTTPS URL to receive events. Must use https://. Example: "https://your-app.com/webhooks/contro"
webhookSecretstringSecret for HMAC-SHA256 signature verification. Min 16 characters. Example: "whsec_your_secret_min_16_chars"
subscribedEventsstring[]Event types to subscribe to. Example: ["card.transaction", "cardholder.kyc.updated"]

Event types

EventDescription
card.transactionA card transaction occurred
card.status.changedA card was activated, frozen, unfrozen, or cancelled
cardholder.kyc.updatedKYC status changed (approved, rejected)
cardholder.createdA new cardholder was created
balance.lowBalance fell below threshold
balance.top_upBalance 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:
AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry24 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

ParameterTypeDescription
cursorstringPagination cursor from the previous response’s nextCursor
limitintegerItems per page (1–100, default 20). Example: 50
statusstringFilter by delivery status. One of pending, delivered, failed
eventTypestringFilter by event type. Example: "card.transaction"

Event fields

FieldTypeDescription
idstringEvent ID. Example: "evt_abc123"
eventTypestringEvent type. One of card.transaction, card.status.changed, cardholder.kyc.updated, cardholder.created, balance.low, balance.top_up
statusstringDelivery status. One of pending, delivered, failed
attemptCountnumberNumber of delivery attempts made. Example: 3
lastAttemptAtstring | nullISO 8601 timestamp of last delivery attempt, or null. Example: "2026-03-20T14:30:00Z"
nextRetryAtstring | nullISO 8601 timestamp of the next scheduled retry, or null if no retry is pending. Example: "2026-03-20T15:00:00Z"
lastResponseStatusnumber | nullHTTP status code from the last delivery attempt, or null. Example: 500
createdAtstringISO 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"