> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.thepennyinc.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.thepennyinc.com/_mcp/server.

# Overview & Delivery Behavior

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](/platform/subscribing-to-webhooks) to set one up.

> **Info**
>
> This page covers asynchronous event delivery. For the separate synchronous request Penny sends during authorization, see [Live Decisioning](/platform/webhooks-live-decisioning).

## The envelope

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

```jsonc
{
  "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](/platform/webhooks-event-catalog) for the full list of `object`- and `alert`-category events. `data`'s shape depends on `category`:

| Category  | Used for                                                                      | Shape                                                                              |
| --------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `object`  | Something was created, updated, or deleted                                    | `operation`, `subject`, `object`, and `previous_attributes` when something changed |
| `alert`   | A threshold or derived condition was crossed (for example, a low balance)     | `severity`, `subject`, `message`, `details`                                        |
| `message` | A platform-originated, human-facing notice not tied to a single object change | `topic`, `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.

| Attempt       | Delay before this attempt | Approximate time since the first attempt |
| ------------- | ------------------------- | ---------------------------------------- |
| 1 (initial)   | —                         | 0 s                                      |
| 2 (retry 1)   | 10 seconds                | 10 s                                     |
| 3 (retry 2)   | 30 seconds                | 40 s                                     |
| 4 (retry 3)   | 1 minute                  | 1 m 40 s                                 |
| 5 (retry 4)   | 5 minutes                 | 6 m 40 s                                 |
| 6 (retry 5)   | 15 minutes                | 21 m 40 s                                |
| 7 (retry 6)   | 1 hour                    | 1 h 21 m                                 |
| 8 (retry 7)   | 2 hours                   | 3 h 21 m                                 |
| 9 (retry 8)   | 4 hours                   | 7 h 21 m                                 |
| 10 (retry 9)  | 8 hours                   | 15 h 21 m                                |
| 11 (retry 10) | 24 hours                  | 39 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](/banking/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:

| Status                 | Meaning                                                                                       | What to do                                                                         |
| ---------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `pending_setup`        | Infrastructure is being provisioned                                                           | Wait and poll                                                                      |
| `setup_failed`         | Provisioning failed                                                                           | Contact Penny Support                                                              |
| `pending_verification` | Ready to verify                                                                               | Trigger and complete verification                                                  |
| `verification_failed`  | The challenge wasn't returned correctly                                                       | Check `verification.reason`; re-trigger verification                               |
| `updating`             | A configuration change is being applied                                                       | Wait and poll                                                                      |
| `active`               | Verified and delivering events                                                                | Normal operation                                                                   |
| `deactivated`          | Manually paused                                                                               | Call `PATCH /notifications/webhooks/{notification_endpoint_id}/activate` to resume |
| `suspended`            | Suspended by Penny                                                                            | Check `suspension.reason`                                                          |
| `archived`             | Archived with `DELETE /notifications/webhooks/{notification_endpoint_id}`; no longer delivers | Create a new endpoint if needed                                                    |

See [Subscribing to Webhooks](/platform/subscribing-to-webhooks#2-wait-for-provisioning) 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](/platform/webhook-authorization) and [HMAC signature](/platform/webhook-hmac) 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](/platform/subscribing-to-webhooks#3-verify-the-endpoint) 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:

| Field                                  | Meaning                                                                                                                                                                                                                             |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`                               | `pending` (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_count`                          | Number of retries scheduled so far.                                                                                                                                                                                                 |
| `last_attempt_time` / `retry_due_time` | When the latest attempt was made, and when the next one is due.                                                                                                                                                                     |
| `failure`                              | `failed` and `reason` for the latest failed attempt.                                                                                                                                                                                |
| `response`                             | `status_code` and `text` from your endpoint's latest response.                                                                                                                                                                      |