# Webhooks
Source: https://fragment.dev/api-reference/webhooks
[Documentation index](https://fragment.dev/llms.txt)
Fragment sends an HTTP POST to your server when a Payment event occurs, such as settlement or refund. Create a subscription to register your receiving URL.
## Subscriptions
Subscription operations follow the API's standard [error handling](https://fragment.dev/api-reference/ledger-overview.md#errors).
### createWebhookSubscription
Call `createWebhookSubscription` with an HTTPS URL, `eventTypes: ["*"]`, and an idempotency key (IK). The authenticated API client determines the workspace.
```graphql title="Create a subscription"
mutation CreateWebhookSubscription($ik: SafeString!, $input: CreateWebhookSubscriptionInput!) {
createWebhookSubscription(ik: $ik, input: $input) {
__typename
... on CreateWebhookSubscriptionResult {
subscription { id webhookVersion }
signingSecret
}
... on Error { code message retryable }
}
}
```
The following subscription receives all event types from every Ledger in its workspace.
```json title="Create variables"
{
"ik": "payment-events-webhook",
"input": {
"url": "https://example.com/webhooks/fragment",
"eventTypes": ["*"]
}
}
```
Save `signingSecret` securely on your backend. The API returns it only when the subscription is first created.
| Argument | GraphQL type | Rule |
| ---------------------- | ---------------------- | ------------------------------------------------------ |
| `ik` | `SafeString!` | Creation idempotency key, unique within the workspace. |
| `input.url` | `String!` | Receiving HTTPS URL. |
| `input.eventTypes` | `[WebhookEventType!]!` | Must be `["*"]`; named event filters are unsupported. |
| `input.webhookVersion` (optional) | `String! = "1"` | Defaults to the latest version at creation. |
On success, the mutation returns:
| Result field | GraphQL type | Meaning |
| --------------- | ---------------------- | ------------------------------------------------ |
| `subscription` | `WebhookSubscription!` | The created or existing subscription. |
| `signingSecret` | `String` | Secret on first creation; null on replay. |
| `isIkReplay` | `Boolean!` | Whether the request repeats an earlier creation. |
#### Creation retries
Follow the API's [idempotency rules](https://fragment.dev/api-reference/ledger-overview.md#idempotency), with these webhook-specific differences:
- Creation keys belong to the workspace.
- Reusing a key with a different URL or webhook version returns `BadRequestError`.
- Successful retries return the existing subscription with `isIkReplay: true` and `signingSecret: null`.
- Deleted subscriptions keep their creation keys reserved. Reusing those keys returns `BadRequestError`.
### webhookSubscriptions
Call `webhookSubscriptions` to list active subscriptions in the authenticated workspace. Deleted subscriptions are excluded.
```graphql title="List subscriptions"
query ListWebhookSubscriptions($first: Int, $after: String) {
webhookSubscriptions(first: $first, after: $after) {
nodes {
id
url
eventTypes
webhookVersion
created
}
pageInfo { endCursor hasNextPage hasPreviousPage startCursor }
}
}
```
Pass `endCursor` as `after` while `hasNextPage` is true. Each node has the fields below.
| Subscription field | GraphQL type |
| ------------------ | ---------------------- |
| `id` | `ID!` |
| `url` | `String!` |
| `eventTypes` | `[WebhookEventType!]!` |
| `webhookVersion` | `String!` |
| `created` | `DateTime!` |
### deleteWebhookSubscription
Call `deleteWebhookSubscription` with the subscription ID. Repeating deletion returns `success: true`.
```graphql title="Delete a subscription"
mutation DeleteWebhookSubscription($id: ID!) {
deleteWebhookSubscription(id: $id) {
__typename
... on DeleteWebhookSubscriptionResult { success }
... on Error { code message retryable }
}
}
```
Deletion stops queued deliveries and future retries. A request already in flight may finish.
- The creation IK stays reserved: reusing it returns `BadRequestError`, so creating another subscription requires a new IK.
On success, the mutation returns:
| Result field | GraphQL type | Meaning |
| --- | --- | --- |
| `success` | `Boolean!` | True when the subscription is deleted, including repeated deletion. |
## Event payload
Fragment posts one event per request to the subscription URL. This example shows a settlement notification.
```json title="Settlement notification"
{
"version": "1",
"type": "payment.settled",
"id": "example-event-id",
"occurredAt": "2026-10-27T18:30:00.000Z",
"data": {
"workspace": { "id": "example-workspace-id" },
"ledger": { "id": "example-ledger-id", "ik": "customer-42" },
"schema": { "key": "invoicing" },
"payment": { "id": "example-payment-id", "ik": "invoice-1024" },
"event": {
"paymentStatus": "settled",
"ledgerEntry": { "id": "example-entry-id" }
}
}
}
```
Each event contains these fields.
| Field | JSON type | Meaning |
| ---------------- | ---------------------------- | --------------------------------------------------------------- |
| `id` | String. | Payment event ID; deduplicate within the workspace. |
| `type` | String. | Event name from the catalog below. |
| `version` | String, exactly `"1"`. | Envelope and event-data format version. |
| `occurredAt` | ISO 8601 string. | Source event time, independent of delivery time. |
| `data.workspace` | `{ id: string }` | Owning workspace. |
| `data.ledger` | `{ id: string, ik: string }` | Ledger ID and its customer-supplied key. |
| `data.schema` | `{ key: string }` | Owning Ledger's schema key. |
| `data.payment` | `{ id: string, ik: string }` | Payment ID and its idempotency key. |
| `data.event` | Object. | Event-specific fields from the catalog below. |
## Payment events
Fragment sends webhook notifications for the following Payment events. All include [`paymentStatus`](https://fragment.dev/api-reference/ledger-types.md#scalars-and-enums-paymentstatus) in `data.event`.
| Event type | Additional fields |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `payment.created` | None. |
| `payment.checkout` | `attempt: { id: string } or null`, `attemptStatus`: [PaymentAttemptStatus](https://fragment.dev/api-reference/ledger-types.md#scalars-and-enums-paymentattemptstatus) or null. |
| `payment.retry` | `attempt: { id: string }`, `attemptStatus`: [PaymentAttemptStatus](https://fragment.dev/api-reference/ledger-types.md#scalars-and-enums-paymentattemptstatus). |
| `payment.card_captured` | `attempt: { id: string } or null`, `attemptStatus`: [PaymentAttemptStatus](https://fragment.dev/api-reference/ledger-types.md#scalars-and-enums-paymentattemptstatus) or null, `ledgerEntry: { id: string } or null`. |
| `payment.card_declined` | `attempt: { id: string }`, `attemptStatus`: [PaymentAttemptStatus](https://fragment.dev/api-reference/ledger-types.md#scalars-and-enums-paymentattemptstatus), `declineCode: string or null`. |
| `payment.card_3ds_canceled` | None. |
| `payment.settled` | `ledgerEntry: { id: string }`. |
| `payment.refund_initiated` | `refund: { id: string, ik: string }`, `refundStatus`: [PaymentRefundStatus](https://fragment.dev/api-reference/ledger-types.md#scalars-and-enums-paymentrefundstatus), `ledgerEntry: { id: string }`. |
| `payment.refund_settled` | `refund: { id: string, ik: string }`, `refundStatus`: [PaymentRefundStatus](https://fragment.dev/api-reference/ledger-types.md#scalars-and-enums-paymentrefundstatus). |
| `payment.refund_failed` | `refund: { id: string, ik: string }`, `refundStatus`: [PaymentRefundStatus](https://fragment.dev/api-reference/ledger-types.md#scalars-and-enums-paymentrefundstatus), `ledgerEntry: { id: string }`. |
## Respond to events
Return HTTP `2xx` after saving the event or finishing processing. Fragment retries requests after a network error, timeout, or non-`2xx` response.
- Events can arrive more than once or out of order.
- Use the workspace ID and event `id` to avoid processing duplicates.
- Retries contain the same event body, including its `id` and `occurredAt`.
- Return HTTP `2xx` for event types your application does not handle.