# 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.