Overview & Delivery Behavior

What a webhook delivery looks like, and how Penny handles failures.

Webhooks let you react to changes on Penny the moment they happen, instead of polling. Every subscription, verification, and delivery-history lookup lives on Penny Banking — even for events about Issuing objects like cards and cardholders. See Subscribing to Webhooks to set one up.

This page covers asynchronous event delivery. For the separate synchronous request Penny sends during authorization, see Live Decisioning.

The envelope

Every delivery is a POST of one JSON object to the URL you registered. Abridged example:

{
"event_id": "notification_event_0193726c-a65e-76c3-8bfb-a65e6c3bfb2f",
"business_id": "business_01937b95-484e-75af-8c68-484e5afc68d3",
"type": "card.issued",
"category": "object",
"schema_version": "1.0",
"event_time": "2026-08-25T14:03:11.123456+00:00",
"occurrence_time": "2026-08-25T14:03:10.987654+00:00",
"data": {
"category": "object",
"operation": "created",
"subject": { "object_type": "card", "object_id": "card_019375de-a2a0-7f31-884a-a2a0f3184abd" },
"object": {
"card_id": "card_019375de-a2a0-7f31-884a-a2a0f3184abd"
// … the rest of the card object
}
}
}

type tells you which kind of event this is — see the Event Catalog for the full list of object- and alert-category events. data’s shape depends on category:

CategoryUsed forShape
objectSomething was created, updated, or deletedoperation, subject, object, and previous_attributes when something changed
alertA threshold or derived condition was crossed (for example, a low balance)severity, subject, message, details
messageA platform-originated, human-facing notice not tied to a single object changetopic, title, body, severity, and an optional subject, details

message events don’t have a fixed set of type identifiers the way object/alert events do — handle them by category rather than expecting them in the Event Catalog.

Delivery behavior

  • Timeout — Penny waits 5 seconds for your endpoint to respond. Respond quickly and process asynchronously if the work takes longer than that.
  • Retries — a non-2xx response, timeout, or connection error schedules a retry. Penny makes up to 10 retries (11 attempts in total) on the schedule below. Each delay varies by up to ±10% so retries don’t arrive in lockstep. If the final retry also fails, the delivery is marked failed and no further attempts are made.
AttemptDelay before this attemptApproximate time since the first attempt
1 (initial)—0 s
2 (retry 1)10 seconds10 s
3 (retry 2)30 seconds40 s
4 (retry 3)1 minute1 m 40 s
5 (retry 4)5 minutes6 m 40 s
6 (retry 5)15 minutes21 m 40 s
7 (retry 6)1 hour1 h 21 m
8 (retry 7)2 hours3 h 21 m
9 (retry 8)4 hours7 h 21 m
10 (retry 9)8 hours15 h 21 m
11 (retry 10)24 hours39 h 21 m
  • Event history — every event Penny has generated for your business is queryable on Penny Banking. GET /notifications/webhooks/events lists events; pass the optional identifier query parameter (an event type such as transaction.created) to return only events of that type. To inspect a single event, call GET /notifications/webhooks/events/{notification_event_id}, which also returns the exact payload that was delivered. These endpoints are listed under Platform › Notifications in the Banking API reference.
  • Ordering isn’t guaranteed — use the resource’s own state (for example, a transaction’s stage, from Transactions) rather than assuming events arrive in the order they occurred.

Endpoint lifecycle

A notification endpoint moves through a status field with nine possible values as it’s created, verified, and operated:

StatusMeaningWhat to do
pending_setupInfrastructure is being provisionedWait and poll
setup_failedProvisioning failedContact Penny Support
pending_verificationReady to verifyTrigger and complete verification
verification_failedThe challenge wasn’t returned correctlyCheck verification.reason; re-trigger verification
updatingA configuration change is being appliedWait and poll
activeVerified and delivering eventsNormal operation
deactivatedManually pausedCall PATCH /notifications/webhooks/{notification_endpoint_id}/activate to resume
suspendedSuspended by PennyCheck suspension.reason
archivedArchived with DELETE /notifications/webhooks/{notification_endpoint_id}; no longer deliversCreate a new endpoint if needed

See Subscribing to Webhooks for the full registration-to-verification flow, including how to poll for these transitions.

Handling duplicate deliveries

Because a slow or failing response is retried, the same event_id can arrive more than once. Use it as a dedupe key — if you’ve already processed an event with that ID, it’s safe to acknowledge and discard the retry rather than reprocess it.

Verifying who sent it

For inbound requests, check the endpoint’s configured bearer token and HMAC signature before processing the body. The same checks apply to event deliveries and Live Decisioning requests. Signing is optional until you create a signing secret.

Confirming you control the endpoint

A newly created webhook won’t deliver anything until it’s verified — Penny sends a challenge to the URL you registered, and you submit the code back. See Subscribing to Webhooks for the exact calls.

Delivery status

Status belongs to each delivery, not to the event. One event produces one delivery per matching endpoint, and each delivery tracks its own attempts. Use GET /notifications/webhooks/events/{notification_event_id}/deliveries to list an event’s deliveries, or GET /notifications/webhooks/{notification_endpoint_id}/deliveries to list deliveries to one endpoint. Both are paginated and return only deliveries belonging to your business.

Each delivery includes:

FieldMeaning
statuspending (no attempt has resolved yet), pending_retry (the latest attempt failed and another is scheduled), delivered (your endpoint returned a 2xx), or failed (retries are exhausted or the endpoint is no longer active).
retry_countNumber of retries scheduled so far.
last_attempt_time / retry_due_timeWhen the latest attempt was made, and when the next one is due.
failurefailed and reason for the latest failed attempt.
responsestatus_code and text from your endpoint’s latest response.