> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fortepayments.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Subscribe to server-side payment status events from Forte. Webhooks are the authoritative signal for delivering Virtual Digital Goods.

Forte delivers signed webhook events to your backend endpoint whenever a payment status changes. For VDG purchases, deliver the item to the user only after receiving a `payment_approved` event.

## Setup Overview

<Steps>
  <Step title="Authenticate">
    Obtain an [access token](/obtain-access-token) using your client credentials.
  </Step>

  <Step title="Create a subscription">
    Register your webhook endpoint with Forte via `POST /notifications/v1/subscriptions`.
  </Step>

  <Step title="Receive and verify events">
    Forte will POST signed events to your endpoint. Verify the signature using your `signing_key` and respond with `200`.
  </Step>
</Steps>

***

## Managing Subscriptions

### Create a Subscription

```bash theme={null}
POST /notifications/v1/subscriptions
Authorization: Bearer {{access_token}}
Content-Type: application/json

{
  "endpoint": "https://yourdomain.com/webhooks/forte",
  "description": "Production payment events"
}
```

Response:

```json theme={null}
{
  "data": {
    "id": "c83ff48a-cd80-4f02-b63f-2155a27682cd",
    "endpoint": "https://yourdomain.com/webhooks/forte",
    "description": "Production payment events",
    "signing_key": "43dec7af-275d-4c5f-b502-c9772858daab",
    "status": "enabled",
    "version": "1"
  }
}
```

<Warning>
  Store the `signing_key` securely. It is used to verify the authenticity of all incoming webhook events.
</Warning>

<Info>
  **Subscription limits**

  | Environment           | Max enabled subscriptions |
  | --------------------- | ------------------------- |
  | Production            | 1                         |
  | Sandbox / Development | 3                         |

  Attempting to enable a subscription beyond this limit returns `429`.
</Info>

### List Subscriptions

```bash theme={null}
GET /notifications/v1/subscriptions
Authorization: Bearer {{access_token}}
```

### Enable / Disable a Subscription

```bash theme={null}
# Enable
POST /notifications/v1/subscriptions/{id}/enable
Authorization: Bearer {{access_token}}

# Disable
POST /notifications/v1/subscriptions/{id}/disable
Authorization: Bearer {{access_token}}
```

### Update a Subscription

```bash theme={null}
PATCH /notifications/v1/subscriptions/{id}
Authorization: Bearer {{access_token}}
Content-Type: application/json

{
  "endpoint": "https://yourdomain.com/webhooks/forte-v2",
  "description": "Updated description"
}
```

### Delete a Subscription

```bash theme={null}
DELETE /notifications/v1/subscriptions/{id}
Authorization: Bearer {{access_token}}
```

<Warning>
  Subscription deletion is irreversible.
</Warning>

***

## Test a Subscription

Send a test event to confirm your endpoint is reachable:

```bash theme={null}
POST /notifications/v1/subscriptions/{id}/test
Authorization: Bearer {{access_token}}
```

Returns `200` if your endpoint responds with `200`, otherwise `502`.

***

## Receiving Webhooks

Your endpoint must respond with HTTP `200` for every webhook received.

<Warning>
  If Forte does not receive a `200`, it will **retry 3 times** before marking the webhook as failed. Subscriptions that repeatedly fail may be automatically disabled.
</Warning>

### Verifying Signatures

Forte signs all webhooks with **HMAC-SHA256** using the `signing_key` returned when you created the subscription. The signature is included in the `X-Forte-Payments-Webhook-Signature` request header. Always verify this signature before processing any payload.

### Replaying Webhooks

If your endpoint was unavailable during a period, replay all webhooks from that window:

```bash theme={null}
POST /notifications/v1/webhooks/replay?start_timestamp={{utc_timestamp}}&end_timestamp={{utc_timestamp}}
Authorization: Bearer {{access_token}}
```

***

## Webhook Payloads

### Payment Status Flow

```
Created → Approved   (deliver item)
Created → Declined   (do not deliver)
Created → Expired    (do not deliver)
```

`Approved`, `Declined`, and `Expired` are terminal states. No further events follow.

***

### Payment Created

Sent when the user initiates a payment session.

```json theme={null}
{
  "event_type": "payment_created",
  "timestamp": "1725481536",
  "version": 1,
  "event_payload": {
    "payment_intent_id": "eda5d152-b75c-4f44-bb5e-65308518def9",
    "payment_status": "Created",
    "payment_method": "credit",
    "amount": "2.99",
    "currency": "EUR",
    "item_title": "Bundle of Gold",
    "item_desc": "1,000 Gold Coins",
    "customer_id": "3bd8c4f5-d25e-4260-b401-c5ce4e8bcc9a"
  }
}
```

### Payment Approved

Sent when payment settles. **Deliver the item to the user upon receiving this event.**

```json theme={null}
{
  "event_type": "payment_approved",
  "timestamp": "1725481561",
  "version": 1,
  "event_payload": {
    "payment_intent_id": "eda5d152-b75c-4f44-bb5e-65308518def9",
    "payment_status": "Approved",
    "payment_method": "credit",
    "amount": "2.99",
    "currency": "EUR",
    "item_title": "Bundle of Gold",
    "item_desc": "1,000 Gold Coins",
    "customer_id": "3bd8c4f5-d25e-4260-b401-c5ce4e8bcc9a"
  }
}
```

### Payment Declined

Sent when the card is declined or a crypto transaction fails.

```json theme={null}
{
  "event_type": "payment_declined",
  "timestamp": "1725481601",
  "version": 1,
  "event_payload": {
    "payment_intent_id": "b3e6a9bb-54f8-4fb5-8732-c3b334ec1a39",
    "payment_status": "Declined",
    "payment_method": "credit",
    "amount": "2.99",
    "currency": "EUR",
    "item_title": "Bundle of Gold",
    "item_desc": "1,000 Gold Coins",
    "customer_id": "3bd8c4f5-d25e-4260-b401-c5ce4e8bcc9a"
  }
}
```

<Note>
  If a card is declined before a 3DS challenge is presented, `payment_declined` fires immediately with no preceding `payment_created` event.
</Note>

### Payment Expired

Sent when the user is inactive for 30 minutes and the session times out.

```json theme={null}
{
  "event_type": "payment_intent_expired",
  "timestamp": "1725481601",
  "version": 1,
  "event_payload": {
    "payment_intent_id": "afb168c8-b951-429f-9ccf-83bf3e03f6e3",
    "payment_status": "Expired",
    "payment_method": null,
    "amount": "2.99",
    "currency": "EUR",
    "item_title": "Bundle of Gold",
    "item_desc": "1,000 Gold Coins",
    "customer_id": "3bd8c4f5-d25e-4260-b401-c5ce4e8bcc9a"
  }
}
```
