> For the complete documentation index, see [llms.txt](https://developer.paddle.com/llms.txt).

# How webhooks work

Understand Paddle's event model, webhook payload structure, and delivery guarantees before you build your integration.

---

Paddle uses webhooks to push real-time notifications to your server when things happen in your account. Rather than polling the API for changes, your server receives an HTTP `POST` request the moment an event occurs.

Use webhooks to:

- Manage access to features in your app depending on a customer's subscription status.
- Sync information with other systems that your business uses, like a CRM or ERP solution.
- Set up notifications or automations.

## The event model

When something notable happens in Paddle, it creates an **event** entity. An event records:

- What happened (`event_type`)
- When it happened (`occurred_at`)
- A snapshot of the entity at that moment (`data`)

You can poll the `/events` endpoint in the Paddle API to get a list of events. However, subscribing to events via webhooks is recommended. Paddle pushes events to you as they happen, rather than you having to pull them.

When you create a [notification destination](https://developer.paddle.com/webhooks/notification-destinations.md), you tell Paddle which events you want to receive and where to send them. From that point on, every time a matching event occurs, Paddle sends a **notification** to your endpoint with the event payload as JSON.

```mermaid
sequenceDiagram
    participant Paddle
    participant YourServer as Your server

    Paddle->>Paddle: Something happens (e.g. subscription created)
    Paddle->>Paddle: Creates an event entity
    Paddle->>YourServer: POST /your-webhook-endpoint
    note over YourServer: Receives JSON payload
    YourServer-->>Paddle: HTTP 200 OK
    note over Paddle: Marks notification as delivered
```

## Webhook payloads

Every webhook Paddle sends has the same top-level structure, regardless of the event type:

| Field | Description |
|---|---|
| `event_id` | Unique ID for this event, prefixed with `evt_`. Use this to deduplicate events you may receive more than once. |
| `event_type` | The type of event, in the format `entity.action` (e.g. `customer.created`). Use this to route events in your handler. |
| `occurred_at` | RFC 3339 timestamp of when the event occurred. Use this to handle events that arrive out of order. |
| `notification_id` | Unique ID for this delivery attempt, prefixed with `ntf_`. Different from `event_id` because a single event can produce multiple notifications. |
| `data` | The new or changed entity at the time of the event. This is a snapshot that reflects the entity's state when the event occurred. |

## Events vs. notifications

An **event** is a record of something that happened. A **notification** is a delivery attempt for that event to a specific destination. Notifications can be delivered via webhook or email.

The distinction matters when you have multiple notification destinations. If you have two webhook endpoints both subscribed to `subscription.created`, Paddle creates one event but two notifications — one for each destination. Each notification has its own `notification_id` but they share the same `event_id`.

## Delivery guarantees

Paddle guarantees **at-least-once delivery**. If your server doesn't respond with `200` within five seconds, Paddle retries the notification automatically. This means your handler may occasionally receive the same event more than once.

To handle this safely, make your webhook processing **idempotent**. This means processing the same event twice should produce the same result as processing it once. Using `event_id` as a deduplication key is the simplest approach: store it when you first process an event, and skip any event with an ID you've already seen.

Because events can arrive out of order, use `occurred_at` rather than arrival time when you need to reason about the sequence of events. For example, if you receive both `subscription.updated` and `subscription.canceled` for the same subscription, compare their `occurred_at` values to determine which represents the latest state.

## Notification status

Each notification moves through a lifecycle as Paddle attempts to deliver it:

```mermaid
flowchart LR
    A["not_attempted"] --> B["needs_retry"]
    A --> C["delivered"]
    B --> C
    B --> D["failed"]
```

| Status | Description |
|---|---|
| `not_attempted` | Paddle hasn't yet tried to deliver this notification. |
| `needs_retry` | Delivery failed. The notification is scheduled for another attempt using an exponential backoff schedule. |
| `delivered` | Your server responded with `200`. Paddle considers this notification successfully delivered. |
| `failed` | All delivery attempts were exhausted. The notification won't be retried automatically, but you can [replay it](https://developer.paddle.com/api-reference/notifications/replay-notification.md) using the API. |

You can inspect notification status and delivery logs in the Paddle dashboard under **Developer Tools > Notifications**, or by using the [list notification logs](https://developer.paddle.com/api-reference/notification-logs/list-notification-logs.md) operation.

## What happens at checkout

When a customer completes Paddle Checkout, a sequence of events fires automatically as Paddle creates and updates entities in your account. Understanding this sequence helps you know which events to handle for provisioning.

Paddle Checkout supports prefilling data and a variety of configuration options, and customer journeys can vary, but a typical sequence of events might look like this:

```mermaid
sequenceDiagram
    participant Customer
    participant Paddle
    participant YourServer as Your server

    Customer->>Paddle: Opens checkout
    Paddle->>YourServer: transaction.created
    Customer->>Paddle: Enters email
    Paddle->>YourServer: customer.created
    Customer->>Paddle: Enters country / ZIP
    Paddle->>YourServer: address.created
    Customer->>Paddle: Enters tax/VAT number (optional)
    Paddle->>YourServer: business.created
    Customer->>Paddle: Completes payment
    Paddle->>YourServer: transaction.paid
    Paddle->>YourServer: subscription.created (if recurring)
    Paddle->>YourServer: transaction.completed
```

For a subscription, `transaction.paid` is the earliest point at which you can be sure payment has been captured. Use `subscription.created` to record the subscription in your system, and `transaction.completed` to confirm that Paddle has finished all internal processing.

You can use the [subscription created scenario](https://developer.paddle.com/webhooks/scenarios/subscription-created.md) in the webhook simulator to test your checkout integration end-to-end. You can inspect each event in order, replay individual notifications, and validate your handler logic without making a real purchase.

## Next steps

{% steps %}
{% step-item title="Create a notification destination" %}
Tell Paddle where to send webhooks and which events you want to receive. Go to **Developer Tools > Notifications** in your Paddle dashboard, or use the [create notification destination](https://developer.paddle.com/api-reference/notification-settings/create-notification-setting.md) operation.

[Create a notification destination](https://developer.paddle.com/webhooks/notification-destinations.md)
{% /step-item %}
{% step-item title="Handle webhook delivery" %}
Configure your server to accept `POST` requests over HTTPS, respond with `200` within five seconds, and handle retries. Learn about IP allowlisting and processing recommendations.

[Handle webhook delivery](https://developer.paddle.com/webhooks/respond-to-webhooks.md)
{% /step-item %}
{% step-item title="Verify webhook signatures" %}
Every Paddle webhook includes a `Paddle-Signature` header. Verify it to confirm that the request genuinely came from Paddle and hasn't been tampered with.

[Verify webhook signatures](https://developer.paddle.com/webhooks/signature-verification.md)
{% /step-item %}
{% step-item title="Test your integration" %}
Use the webhook simulator to send test events to your endpoint without triggering real transactions.

[Simulate webhooks](https://developer.paddle.com/webhooks/test-webhooks.md)
{% /step-item %}
{% /steps %}