> For the complete documentation index, see [llms.txt](https://docs.petje.af/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.petje.af/petje.af-api/reference/webhooks.md).

# Webhooks

### Event types

A webhook can subscribe to one or more of the following events:

| Event                 | Description                                |
| --------------------- | ------------------------------------------ |
| `new_onetime_payment` | A new one-time payment has been paid       |
| `new_membership`      | A membership has been activated            |
| `membership_canceled` | A membership has been canceled             |
| `webhook_deleted`     | The webhook configuration has been deleted |

***

### Get webhooks

<mark style="color:blue;">`GET`</mark> `https://api.petje.af/v1/pages/:pageId/webhooks`

Returns the webhooks belonging to the selected page.

Scope: `webhooks.read`

#### Path Parameters

| Name     | Type   | Description    |
| -------- | ------ | -------------- |
| `pageId` | string | ID of the page |

#### Query Parameters

| Name    | Type    | Description                                             |
| ------- | ------- | ------------------------------------------------------- |
| `limit` | integer | Number of webhooks to return. Must be between 1 and 250 |
| `after` | string  | Return results after the webhook with this ID           |

#### Headers

| Name            | Type   | Description                            |
| --------------- | ------ | -------------------------------------- |
| `Authorization` | string | Access token using the `Bearer` method |

#### Responses

**200 — Webhooks successfully retrieved**

```json
{
    "count": 2,
    "_embedded": {
        "webhooks": [
            {
                "resource": "webhooks",
                "id": "8bcd47fa-6623-4bd9-98d5-f0b460f2f0f8",
                "url": "https://example.com/webhooks/petjeaf",
                "secret": "generated-webhook-secret",
                "events": [
                    "new_onetime_payment",
                    "new_membership"
                ],
                "_links": {
                    "self": {
                        "href": "https://api.petje.af/v1/pages/5SaEUOWD2b4dN7tJBKMB/webhooks/8bcd47fa-6623-4bd9-98d5-f0b460f2f0f8",
                        "type": "application/hal+json"
                    },
                    "page": {
                        "href": "https://api.petje.af/v1/pages/5SaEUOWD2b4dN7tJBKMB",
                        "type": "application/hal+json"
                    }
                }
            }
        ]
    },
    "_links": {
        "self": {
            "href": "https://api.petje.af/v1/pages/5SaEUOWD2b4dN7tJBKMB/webhooks",
            "type": "application/hal+json"
        },
        "next": null
    }
}
```

**403 — Missing scope or page access**

***

### Get webhook detail

<mark style="color:blue;">`GET`</mark> `https://api.petje.af/v1/pages/:pageId/webhooks/:id`

Returns a single webhook belonging to the selected page.

Scope: `webhooks.read`

#### Path Parameters

| Name     | Type   | Description       |
| -------- | ------ | ----------------- |
| `pageId` | string | ID of the page    |
| `id`     | string | ID of the webhook |

#### Headers

| Name            | Type   | Description                            |
| --------------- | ------ | -------------------------------------- |
| `Authorization` | string | Access token using the `Bearer` method |

#### Responses

**200 — Webhook successfully retrieved**

```json
{
    "resource": "webhooks",
    "id": "8bcd47fa-6623-4bd9-98d5-f0b460f2f0f8",
    "url": "https://example.com/webhooks/petjeaf",
    "secret": "generated-webhook-secret",
    "events": [
        "new_onetime_payment",
        "new_membership"
    ],
    "_links": {
        "self": {
            "href": "https://api.petje.af/v1/pages/5SaEUOWD2b4dN7tJBKMB/webhooks/8bcd47fa-6623-4bd9-98d5-f0b460f2f0f8",
            "type": "application/hal+json"
        },
        "page": {
            "href": "https://api.petje.af/v1/pages/5SaEUOWD2b4dN7tJBKMB",
            "type": "application/hal+json"
        }
    }
}
```

**403 — Missing scope or page access**

**404 — Webhook not found or does not belong to the page**

***

### Create webhook

`POST` `https://api.petje.af/v1/pages/:pageId/webhooks`

Creates a webhook for the selected page. The webhook secret is generated automatically and cannot be supplied by the client.

Scope: `webhooks.create` or `webhooks.write`

#### Path Parameters

| Name     | Type   | Description    |
| -------- | ------ | -------------- |
| `pageId` | string | ID of the page |

#### Headers

| Name            | Type   | Description                            |
| --------------- | ------ | -------------------------------------- |
| `Authorization` | string | Access token using the `Bearer` method |
| `Content-Type`  | string | `application/json`                     |

#### Request Body

| Name     | Type   | Required | Description                               |
| -------- | ------ | -------- | ----------------------------------------- |
| `url`    | string | Yes      | URL to which webhook requests are sent    |
| `events` | array  | Yes      | One or more supported webhook event types |

```json
{
    "url": "https://example.com/webhooks/petjeaf",
    "events": [
        "new_onetime_payment",
        "new_membership",
        "membership_canceled",
        "webhook_deleted"
    ]
}
```

#### Responses

**201 — Webhook successfully created**

```json
{
    "resource": "webhooks",
    "id": "8bcd47fa-6623-4bd9-98d5-f0b460f2f0f8",
    "url": "https://example.com/webhooks/petjeaf",
    "secret": "generated-webhook-secret",
    "events": [
        "new_onetime_payment",
        "new_membership",
        "membership_canceled",
        "webhook_deleted"
    ],
    "_links": {
        "self": {
            "href": "https://api.petje.af/v1/pages/5SaEUOWD2b4dN7tJBKMB/webhooks/8bcd47fa-6623-4bd9-98d5-f0b460f2f0f8",
            "type": "application/hal+json"
        },
        "page": {
            "href": "https://api.petje.af/v1/pages/5SaEUOWD2b4dN7tJBKMB",
            "type": "application/hal+json"
        }
    }
}
```

**403 — Missing scope or page access**

**422 — Validation failed**

The following conditions cause a validation error:

* `url` is missing or is not a valid URL.
* `events` is missing or empty.
* `events` contains an unsupported or duplicate event type.

***

### Delete webhook

`DELETE` `https://api.petje.af/v1/pages/:pageId/webhooks/:id`

Deletes a webhook belonging to the selected page.

If the webhook subscribes to `webhook_deleted`, a final asynchronous request is sent to the configured URL after deletion.

Scope: `webhooks.delete` or `webhooks.write`

#### Path Parameters

| Name     | Type   | Description       |
| -------- | ------ | ----------------- |
| `pageId` | string | ID of the page    |
| `id`     | string | ID of the webhook |

#### Headers

| Name            | Type   | Description                            |
| --------------- | ------ | -------------------------------------- |
| `Authorization` | string | Access token using the `Bearer` method |

#### Responses

**204 — Webhook successfully deleted**

The response does not contain a body.

**403 — Missing scope or page access**

**404 — Webhook not found or does not belong to the page**

***

## Receiving webhook requests

Webhook requests are sent as HTTP `POST` requests containing JSON.

### Request headers

| Header                        | Description                                   |
| ----------------------------- | --------------------------------------------- |
| `Content-Type`                | `application/json`                            |
| `X-Petjeaf-Webhook-Event`     | The event type                                |
| `X-Petjeaf-Webhook-Signature` | HMAC-SHA256 signature prefixed with `sha256=` |

The signature is created from the raw JSON request body using the webhook secret.

```
sha256=HMAC_SHA256(raw_request_body, webhook_secret)
```

Your endpoint should calculate the signature using the raw request body and compare it using a timing-safe comparison.

Failed webhook deliveries are retried up to three times.

### One-time payment payload

Event: `new_onetime_payment`

```json
{
    "event": "new_onetime_payment",
    "data": {
        "amount": 5,
        "currency": "EUR",
        "formatted_amount": "€ 5,00",
        "id": "f4476dbc-19fd-4134-a10f-a5893127db97",
        "page": {
            "id": "5SaEUOWD2b4dN7tJBKMB",
            "name": "Petje.af creator",
            "title": "Support my work"
        },
        "user": {
            "name": "John Doe",
            "id": "ea0fcc69-e615-48d2-a0d7-7c08d49988a1"
        },
        "comment": {
            "id": "ad040078-65d7-45aa-b576-78764970e279",
            "comment": "Keep up the good work!",
            "visibility": "public"
        }
    }
}
```

The `comment`, `page`, or `user` value can be `null` when the associated resource is unavailable.

### Membership payload

Events:

* `new_membership`
* `membership_canceled`

```json
{
    "event": "new_membership",
    "data": {
        "amount": 2,
        "currency": "EUR",
        "interval": "month",
        "amount_formatted": "€ 2,00 per maand",
        "id": "9126eabb-3444-4635-aeba-e10876102e70",
        "page": {
            "id": "5SaEUOWD2b4dN7tJBKMB",
            "name": "Petje.af creator",
            "title": "Support my work"
        },
        "user": {
            "name": "Jane Doe",
            "id": "4098f3fe-50ec-4482-8959-8f623764e1dc"
        },
        "comment": {
            "id": "99d44683-bfa4-40da-8214-c44f0b686fa2",
            "comment": "Happy to join!",
            "visibility": "public"
        }
    }
}
```

The `comment`, `page`, or `user` value can be `null` when the associated resource is unavailable.

### Webhook deleted payload

Event: `webhook_deleted`

```json
{
    "event": "webhook_deleted",
    "data": {
        "id": "8bcd47fa-6623-4bd9-98d5-f0b460f2f0f8",
        "page": {
            "id": "5SaEUOWD2b4dN7tJBKMB",
            "name": "Petje.af creator",
            "title": "Support my work"
        }
    }
}
```
