card.operation.failed — delivered when a card operation that was accepted with PROCESSING has
been rejected by the card network.When it fires#
Adjusting a limit, freezing a card and unfreezing a card are asynchronous. The API returns
status: PROCESSING, and the outcome arrives later as one of two events:| Outcome | Event | Card |
|---|
| Applied | card.updated | Shows the new limit or status |
| Rejected by the card network | card.operation.failed | Unchanged — keeps its previous limit and status |
Every accepted operation ends in exactly one of the two. An operation refused synchronously by the
API (a 4xx response) was never accepted and produces neither.Once you receive this event, the card accepts a new operation again; you may retry after fixing the
cause.Payload#
Envelope#
| Field | Type | Description |
|---|
eventId | string | Unique per event. Deduplicate on this value |
eventType | string | Always card.operation.failed |
version | string | Payload schema version. Additive changes do not bump it |
occurredAt | string | When the event happened, not when it was delivered (ISO 8601) |
data | object | See below |
data#
| Field | Type | Description |
|---|
cardNo | string | PIK card number |
operation | string | The operation that failed: ADJUST_CARD_LIMIT / FREEZE_CARD / UNFREEZE_CARD, as returned when you submitted it. null in the rare case PIK cannot match the rejection to a request |
failureReason | string | Human-readable reason, for display and support. Do not parse it. May be null |
card | object | The card as it is now, structurally identical to GET /api/v1/issuing/card/{cardNo}. Because the operation did not take effect, its limit and status are the values from before the operation |
Example#
{
"eventId": "evt_260923P4Q7ZR8M5K",
"eventType": "card.operation.failed",
"version": "1.0",
"occurredAt": "2026-09-23T04:11:31Z",
"data": {
"cardNo": "CD260811X9Y8Z7",
"operation": "ADJUST_CARD_LIMIT",
"failureReason": "Card limit exceeds the maximum allowed for this card product",
"card": {
"cardNo": "CD260811X9Y8Z7",
"cardholderNo": "CH260811A1B2C3",
"maskedCardNumber": "409636******0501",
"status": "ACTIVE",
"cardLimit": "500.00",
"availableLimit": "488.50",
"currency": "USD",
"label": "Ads spend - Q3",
"externalId": "card-req-4471",
"createTimeUtc": "2026-08-11T02:16:02Z"
}
}
}
Verifying the signature#
Three headers accompany every delivery:| Header | Value |
|---|
X-Webhook-Event | Event category, always ISSUING for this event |
X-Webhook-Event-Type | The specific event type, see above |
X-Webhook-Signature | HMAC-SHA256 signature, lowercase hex |
X-Webhook-Signature = hex_lower( HMAC_SHA256( appSecret, rawBody ) )
The signed content is the raw request body only, with no timestamp and no separator. The signing
key is your appSecret — there is no separate webhook secret.1.
Sign the raw bytes of the request body, before any JSON parsing.
2.
Compare in constant time.
3.
Nothing time-based is signed, so a captured delivery stays replayable — deduplicating on
eventId is mandatory, not optional.
Delivery#
Return any 2xx within 10 seconds to acknowledge.
Failed deliveries are retried on a fixed interval: 5 attempts, 5 minutes apart (about
20 minutes in total), after which the event is marked exhausted and never retried again.
This endpoint receives Issuing events only; still return 2xx for event types you do not handle.
Delivery is at least once — deduplicate on eventId.
Ordering is not guaranteed — use occurredAt to decide which version is newer.
See the Webhooks guide for verification code samples and recommended
handling.Modified at 2026-09-30 09:26:54