Webhooks
Configure one receiver endpoint, verify signatures and process AICardAPI events safely.
Endpoints
Each merchant account configures one receiver URL with /v1/webhook-endpoints. AICardAPI sends supported events to that URL automatically; there is no per-event subscription to manage. Signing secrets are shown once at creation and stored only on your server.
Supported events
AICardAPI sends a small, stable set of events. The transaction lifecycle is carried inside card.transaction.updated through data.transaction_type and data.status rather than as separate event types.
- card.transaction.updated — a card transaction was created or updated. data.transaction_type is one of authorization, settlement, refund, reversal, or decline; data.status is one of pending, posted, settled, declined, or reversed.
- card.status.updated — a card status changed (active, suspended, or cancelled).
- merchant.balance.updated — your account balance changed. data.change_type is deposit (a top-up was added to your account) or correction (a balance adjustment). Top-ups are added for you by the platform.
- webhook.test — a validation event that does not represent a real transaction.
Transaction event example
Every event is a JSON envelope with id, type, created_at and a data object. Use the envelope id for deduplication. This example is an approval hold: transaction_type authorization, status pending, settled_at still null.
{
"id": "evt_3Qk7Z2pX9bWm",
"type": "card.transaction.updated",
"created_at": "2026-06-06T18:40:12Z",
"data": {
"transaction_id": "txn_8Vhor2mDeT1k",
"card_id": "card_2yT9pLkQ4ZrN",
"customer_id": "cus_5RbN8xWq2aHe",
"transaction_type": "authorization",
"status": "pending",
"amount": "42.50",
"currency": "USD",
"merchant": { "name": "Globex Cloud", "category": "saas", "country": "US" },
"authorized_at": "2026-06-06T18:40:12Z",
"settled_at": null,
"decline_reason": null
}
}Settlement event example
When an authorization settles, the same transaction_id is re-sent with transaction_type settlement, status posted, settled_at filled, and the final posted amount (which can differ slightly from the authorized amount).
{
"id": "evt_7Lm4Yc8nKpQ2",
"type": "card.transaction.updated",
"created_at": "2026-06-07T02:15:44Z",
"data": {
"transaction_id": "txn_8Vhor2mDeT1k",
"card_id": "card_2yT9pLkQ4ZrN",
"customer_id": "cus_5RbN8xWq2aHe",
"transaction_type": "settlement",
"status": "posted",
"amount": "42.83",
"currency": "USD",
"merchant": { "name": "Globex Cloud", "category": "saas", "country": "US" },
"authorized_at": "2026-06-06T18:40:12Z",
"settled_at": "2026-06-07T02:15:44Z",
"decline_reason": null
}
}Declined transaction example
Declined transactions carry transaction_type decline, status declined and a data.decline_reason; authorized_at and settled_at stay null. Reversals use transaction_type reversal with status reversed.
{
"id": "evt_9Wd2Rf6tJsB4",
"type": "card.transaction.updated",
"created_at": "2026-06-06T19:05:03Z",
"data": {
"transaction_id": "txn_1Az5KpQ7nMx0",
"card_id": "card_2yT9pLkQ4ZrN",
"customer_id": "cus_5RbN8xWq2aHe",
"transaction_type": "decline",
"status": "declined",
"amount": "129.00",
"currency": "USD",
"merchant": { "name": "Initech Travel", "category": "travel", "country": "GB" },
"authorized_at": null,
"settled_at": null,
"decline_reason": "insufficient_card_balance"
}
}Card status event example
card.status.updated reports the new status, the previous status and the change time.
{
"id": "evt_4Hn8Vb3wLpC6",
"type": "card.status.updated",
"created_at": "2026-06-06T20:11:09Z",
"data": {
"card_id": "card_2yT9pLkQ4ZrN",
"status": "suspended",
"previous_status": "active",
"changed_at": "2026-06-06T20:11:09Z"
}
}Account balance event example
merchant.balance.updated tells you when your account balance changes. change_type deposit means a top-up was added to your account; correction means a balance adjustment. data.available_balance is the balance after the change.
{
"id": "evt_6Cn3Wd9pLs0k",
"type": "merchant.balance.updated",
"created_at": "2026-06-06T16:20:05Z",
"data": {
"merchant_id": "mch_4kPq7Z2nXw",
"change_type": "deposit",
"amount": "500.00",
"currency": "USD",
"available_balance": "5250.00",
"reference": "wire-20260606-018"
}
}Test event example
Sending a test event delivers this payload so you can confirm reachability and signature handling end to end.
{
"id": "evt_0Tt1Pe5yNqD8",
"type": "webhook.test",
"created_at": "2026-06-06T18:30:00Z",
"data": {
"test": true,
"message": "Test event sent to verify your endpoint and signature handling. It does not represent a real card transaction."
}
}Signature verification
AICardAPI signs every delivery. Read the timestamp and signature headers, recompute the HMAC over the event id, timestamp and exact request body using your endpoint signing secret, and reject unsigned or stale requests. Use only the AICardAPI signing secret for this endpoint; do not reuse secrets from other systems.
Delivery and retry rules
Deliveries are attempted at least once and retried with backoff on failure or non-2xx responses, up to a fixed attempt limit. Exhausted deliveries move to a dead-letter state for triage. Inspect states with /v1/webhook-deliveries scoped by endpoint_id; each record exposes status, attempt_count and request_id.
{"data":{"id":"delivery_demo","status":"retrying","attempt_count":2},"request_id":"req_demo"}Idempotency
Because deliveries are at-least-once, the same event id can arrive more than once after a retry. Treat the envelope id as the idempotency key: record processed event ids and ignore duplicates so a retried delivery never double-applies an effect.
Responding to deliveries
Acknowledge fast and process asynchronously. Return an HTTP 2xx as soon as you have safely persisted or enqueued the event, then do downstream work in the background. Slow handlers, thrown errors or non-2xx responses are treated as failures and retried.
Replay
Manual replay requires an audit reason and rebuilds the payload from recorded AICardAPI event facts. Replay preserves event_id semantics, so your idempotency handling should recognize a replayed delivery; correlate it with delivery_id and request_id.