# Webhooks — PapaProxy.net API reference

Page: https://papaproxy.net/developers/reference/webhooks.php
Last updated: November 9, 2026
Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/

### List webhook event types

`GET /api/v1/customer/webhooks/events`

- Access: API key required · scope `webhooks`

Event types you can subscribe a webhook endpoint to.

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/webhooks/events" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/webhooks/events",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks/events", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/webhooks/events');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/customer/webhooks/events", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Event types you can subscribe to.

Example response:

```json
{
  "events": [
    "deposit.completed",
    "payment.failed",
    "payment.succeeded",
    "service.expiring",
    "service.provisioned",
    "service.renewed",
    "service.suspended",
    "service.terminated",
    "wallet.balance_low"
  ]
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `events` | array of string | yes | Event types the endpoint is subscribed to. |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `webhooks`.

  Example:

  ```json
  {"detail": "API key is missing required scope: webhooks"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### List webhook endpoints

`GET /api/v1/customer/webhooks`

- Access: API key required · scope `webhooks`

List your webhook endpoints.

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/webhooks" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/webhooks",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/webhooks');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/customer/webhooks", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Your webhook endpoints.

Example response:

```json
[
  {
    "id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7",
    "url": "https://example.com/webhooks/papaproxy",
    "secret_prefix": "whsec_3p",
    "events": [
      "service.provisioned",
      "wallet.balance_low"
    ],
    "description": null,
    "is_active": true,
    "disabled_reason": null,
    "consecutive_failures": 0,
    "created_at": "2026-11-07T14:31:39.000403Z",
    "updated_at": null
  }
]
```

| Field | Type | Required | Description |
|---|---|---|---|
| `[].id` | string (uuid) | yes | Unique ID. |
| `[].url` | string | yes |  |
| `[].secret_prefix` | string | yes | First characters of the signing secret. |
| `[].events` | array of string | yes | Event types the endpoint is subscribed to. |
| `[].description` | string, nullable | no |  |
| `[].is_active` | boolean | yes | Whether it's active. |
| `[].disabled_reason` | string, nullable | no | Why the endpoint was disabled, if it was. |
| `[].consecutive_failures` | integer | no | Failed deliveries in a row. · Default: `0` |
| `[].created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). |
| `[].updated_at` | string (date-time), nullable | no | When it was last changed (ISO 8601, UTC). |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `webhooks`.

  Example:

  ```json
  {"detail": "API key is missing required scope: webhooks"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Create a webhook endpoint

`POST /api/v1/customer/webhooks`

- Access: API key required · scope `webhooks`

Register an HTTPS URL to receive event notifications. The signing secret (`whsec_…`) is returned only once, in this response: store it to verify the `X-FB-Signature` header of each delivery (see Webhooks in the overview).

Only `https` URLs are accepted; an `http` URL returns `422` (`Only https URLs are allowed`). Every value in `events` must come from `GET /api/v1/customer/webhooks/events`; an unknown event type returns `422`.

Request body (JSON):

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | string | yes | Min length: 8 · Max length: 2048 |
| `events` | array of string | yes | Event types the endpoint is subscribed to. · Min items: 1 |
| `description` | string, nullable | no | Max length: 255 |

Request examples:

cURL:

```bash
curl -X POST "https://papaproxy.net/account_new/api/v1/customer/webhooks" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/webhooks/papaproxy", "events": ["service.provisioned", "wallet.balance_low"]}'
```

Python:

```python
import os

import requests

resp = requests.post(
    "https://papaproxy.net/account_new/api/v1/customer/webhooks",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    json={
        "url": "https://example.com/webhooks/papaproxy",
        "events": [
            "service.provisioned",
            "wallet.balance_low"
        ]
    },
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "url": "https://example.com/webhooks/papaproxy",
    "events": [
      "service.provisioned",
      "wallet.balance_low"
    ]
  }),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/webhooks');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'url' => 'https://example.com/webhooks/papaproxy',
        'events' => ['service.provisioned', 'wallet.balance_low']
    ]),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	req, err := http.NewRequest("POST", "https://papaproxy.net/account_new/api/v1/customer/webhooks", strings.NewReader(`{"url": "https://example.com/webhooks/papaproxy", "events": ["service.provisioned", "wallet.balance_low"]}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `201` — Create response — includes the raw secret exactly once.

Example response:

```json
{
  "id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7",
  "url": "https://example.com/webhooks/papaproxy",
  "secret_prefix": "whsec_3p",
  "events": [
    "service.provisioned",
    "wallet.balance_low"
  ],
  "description": null,
  "is_active": true,
  "disabled_reason": null,
  "consecutive_failures": 0,
  "created_at": "2026-11-07T14:31:39.000403Z",
  "updated_at": null,
  "secret": "whsec_3pQ9xVn2LmA7cR8sK1tY5bZ0wE4uH6jD"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string (uuid) | yes | Unique ID. |
| `url` | string | yes |  |
| `secret_prefix` | string | yes | First characters of the signing secret. |
| `events` | array of string | yes | Event types the endpoint is subscribed to. |
| `description` | string, nullable | no |  |
| `is_active` | boolean | yes | Whether it's active. |
| `disabled_reason` | string, nullable | no | Why the endpoint was disabled, if it was. |
| `consecutive_failures` | integer | no | Failed deliveries in a row. · Default: `0` |
| `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). |
| `updated_at` | string (date-time), nullable | no | When it was last changed (ISO 8601, UTC). |
| `secret` | string | yes |  |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `webhooks`.

  Example:

  ```json
  {"detail": "API key is missing required scope: webhooks"}
  ```

- `422` — Validation error, an `http` URL, or an unknown event type.

  Example:

  ```json
  {"detail": "Only https URLs are allowed"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Get a webhook endpoint

`GET /api/v1/customer/webhooks/{endpoint_id}`

- Access: API key required · scope `webhooks`

Get one of your webhook endpoints.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. |

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — The webhook endpoint.

Example response:

```json
{
  "id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7",
  "url": "https://example.com/webhooks/papaproxy",
  "secret_prefix": "whsec_3p",
  "events": [
    "service.provisioned",
    "wallet.balance_low"
  ],
  "description": null,
  "is_active": true,
  "disabled_reason": null,
  "consecutive_failures": 0,
  "created_at": "2026-11-07T14:31:39.000403Z",
  "updated_at": null
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string (uuid) | yes | Unique ID. |
| `url` | string | yes |  |
| `secret_prefix` | string | yes | First characters of the signing secret. |
| `events` | array of string | yes | Event types the endpoint is subscribed to. |
| `description` | string, nullable | no |  |
| `is_active` | boolean | yes | Whether it's active. |
| `disabled_reason` | string, nullable | no | Why the endpoint was disabled, if it was. |
| `consecutive_failures` | integer | no | Failed deliveries in a row. · Default: `0` |
| `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). |
| `updated_at` | string (date-time), nullable | no | When it was last changed (ISO 8601, UTC). |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `webhooks`.

  Example:

  ```json
  {"detail": "API key is missing required scope: webhooks"}
  ```

- `404` — Webhook endpoint not found, or it doesn't belong to your account.
- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Update a webhook endpoint

`PATCH /api/v1/customer/webhooks/{endpoint_id}`

- Access: API key required · scope `webhooks`

Change the URL, event types, or status of a webhook endpoint. The same rules as on creation apply: `https` URLs only, and event types from `GET /api/v1/customer/webhooks/events`.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. |

Request body (JSON):

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | string, nullable | no | Min length: 8 · Max length: 2048 |
| `events` | array of string, nullable | no | Event types the endpoint is subscribed to. · Min items: 1 |
| `description` | string, nullable | no | Max length: 255 |
| `is_active` | boolean, nullable | no | Whether it's active. |

Request examples:

cURL:

```bash
curl -X PATCH "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/webhooks/papaproxy", "events": ["service.provisioned"]}'
```

Python:

```python
import os

import requests

resp = requests.patch(
    "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    json={
        "url": "https://example.com/webhooks/papaproxy",
        "events": [
            "service.provisioned"
        ]
    },
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "url": "https://example.com/webhooks/papaproxy",
    "events": [
      "service.provisioned"
    ]
  }),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'url' => 'https://example.com/webhooks/papaproxy',
        'events' => ['service.provisioned']
    ]),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	req, err := http.NewRequest("PATCH", "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6", strings.NewReader(`{"url": "https://example.com/webhooks/papaproxy", "events": ["service.provisioned"]}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — The updated webhook endpoint.

Example response:

```json
{
  "id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7",
  "url": "https://example.com/webhooks/papaproxy",
  "secret_prefix": "whsec_3p",
  "events": [
    "service.provisioned"
  ],
  "description": null,
  "is_active": true,
  "disabled_reason": null,
  "consecutive_failures": 0,
  "created_at": "2026-11-07T14:31:39.000403Z",
  "updated_at": "2026-11-07T14:31:41.801663Z"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string (uuid) | yes | Unique ID. |
| `url` | string | yes |  |
| `secret_prefix` | string | yes | First characters of the signing secret. |
| `events` | array of string | yes | Event types the endpoint is subscribed to. |
| `description` | string, nullable | no |  |
| `is_active` | boolean | yes | Whether it's active. |
| `disabled_reason` | string, nullable | no | Why the endpoint was disabled, if it was. |
| `consecutive_failures` | integer | no | Failed deliveries in a row. · Default: `0` |
| `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). |
| `updated_at` | string (date-time), nullable | no | When it was last changed (ISO 8601, UTC). |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `webhooks`.

  Example:

  ```json
  {"detail": "API key is missing required scope: webhooks"}
  ```

- `404` — Webhook endpoint not found, or it doesn't belong to your account.
- `422` — Validation error, an `http` URL, or an unknown event type.

  Example:

  ```json
  {"detail": "Only https URLs are allowed"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Delete a webhook endpoint

`DELETE /api/v1/customer/webhooks/{endpoint_id}`

- Access: API key required · scope `webhooks`

Delete a webhook endpoint. Deliveries to it stop immediately.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. |

Request examples:

cURL:

```bash
curl -X DELETE "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.delete(
    "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("DELETE", "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Generic message response.

Example response:

```json
{
  "message": "ok"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `message` | string | yes |  |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `webhooks`.

  Example:

  ```json
  {"detail": "API key is missing required scope: webhooks"}
  ```

- `404` — Webhook endpoint not found, or it doesn't belong to your account.
- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### List webhook deliveries

`GET /api/v1/customer/webhooks/{endpoint_id}/deliveries`

- Access: API key required · scope `webhooks`

Recent delivery attempts for this endpoint, with response status codes. A failed delivery is retried 5 more times over about 8.5 hours, then marked as failed.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. |
| `limit (query)` | integer | no | Page size. · Minimum: 1 · Maximum: 50 · Default: `50` |

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/deliveries" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/deliveries",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/deliveries", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/deliveries');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/deliveries", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Recent deliveries, newest first.

Example response:

```json
[
  {
    "id": "f2ec0963-48bb-52a5-966b-07b7e36a835d",
    "event_id": "1529f6ec-af23-594f-941d-a694f3dde477",
    "endpoint_id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7",
    "event_type": "ping",
    "status": "pending",
    "attempts": 0,
    "next_attempt_at": "2026-11-07T14:31:42.661857Z",
    "last_response_code": null,
    "last_response_body": null,
    "last_error": null,
    "created_at": "2026-11-07T14:31:42.656390Z",
    "updated_at": null
  }
]
```

| Field | Type | Required | Description |
|---|---|---|---|
| `[].id` | string (uuid) | yes | Unique ID. |
| `[].event_id` | string (uuid) | yes |  |
| `[].endpoint_id` | string (uuid) | yes |  |
| `[].event_type` | string | yes |  |
| `[].status` | string | yes |  |
| `[].attempts` | integer | yes |  |
| `[].next_attempt_at` | string (date-time), nullable | no |  |
| `[].last_response_code` | integer, nullable | no |  |
| `[].last_response_body` | string, nullable | no |  |
| `[].last_error` | string, nullable | no |  |
| `[].created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). |
| `[].updated_at` | string (date-time), nullable | no | When it was last changed (ISO 8601, UTC). |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `webhooks`.

  Example:

  ```json
  {"detail": "API key is missing required scope: webhooks"}
  ```

- `404` — Webhook endpoint not found, or it doesn't belong to your account.
- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Send a test event

`POST /api/v1/customer/webhooks/{endpoint_id}/test`

- Access: API key required · scope `webhooks`

Send a `ping` test event to this endpoint to check that it receives and verifies deliveries. The delivery is queued and usually arrives within a minute.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `endpoint_id (path)` | string (uuid) | yes | ID of the webhook endpoint. |

Request examples:

cURL:

```bash
curl -X POST "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/test" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.post(
    "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/test",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/test", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/test');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("POST", "https://papaproxy.net/account_new/api/v1/customer/webhooks/3fa85f64-5717-4562-b3fc-2c963f66afa6/test", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `201` — The test delivery that was created.

Example response:

```json
{
  "id": "f2ec0963-48bb-52a5-966b-07b7e36a835d",
  "event_id": "1529f6ec-af23-594f-941d-a694f3dde477",
  "endpoint_id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7",
  "event_type": "ping",
  "status": "pending",
  "attempts": 0,
  "next_attempt_at": "2026-11-07T14:31:42.661857Z",
  "last_response_code": null,
  "last_response_body": null,
  "last_error": null,
  "created_at": "2026-11-07T14:31:42.656390Z",
  "updated_at": null
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string (uuid) | yes | Unique ID. |
| `event_id` | string (uuid) | yes |  |
| `endpoint_id` | string (uuid) | yes |  |
| `event_type` | string | yes |  |
| `status` | string | yes |  |
| `attempts` | integer | yes |  |
| `next_attempt_at` | string (date-time), nullable | no |  |
| `last_response_code` | integer, nullable | no |  |
| `last_response_body` | string, nullable | no |  |
| `last_error` | string, nullable | no |  |
| `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). |
| `updated_at` | string (date-time), nullable | no | When it was last changed (ISO 8601, UTC). |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `webhooks`.

  Example:

  ```json
  {"detail": "API key is missing required scope: webhooks"}
  ```

- `404` — Webhook endpoint not found, or it doesn't belong to your account.
- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Redeliver an event

`POST /api/v1/customer/webhooks/deliveries/{delivery_id}/redeliver`

- Access: API key required · scope `webhooks`

Send a past delivery again, for example after fixing your endpoint. The event ID stays the same, so your handler can skip events it has already processed.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `delivery_id (path)` | string (uuid) | yes | ID of the delivery. |

Request examples:

cURL:

```bash
curl -X POST "https://papaproxy.net/account_new/api/v1/customer/webhooks/deliveries/3fa85f64-5717-4562-b3fc-2c963f66afa6/redeliver" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.post(
    "https://papaproxy.net/account_new/api/v1/customer/webhooks/deliveries/3fa85f64-5717-4562-b3fc-2c963f66afa6/redeliver",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/webhooks/deliveries/3fa85f64-5717-4562-b3fc-2c963f66afa6/redeliver", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/webhooks/deliveries/3fa85f64-5717-4562-b3fc-2c963f66afa6/redeliver');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("POST", "https://papaproxy.net/account_new/api/v1/customer/webhooks/deliveries/3fa85f64-5717-4562-b3fc-2c963f66afa6/redeliver", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `201` — The new delivery attempt.

Example response:

```json
{
  "id": "fcf4a431-710d-5edd-a55c-9a4efc65da19",
  "event_id": "3db339fc-c187-5608-b68a-7ee93c944424",
  "endpoint_id": "24018d46-98dc-5a0a-8ca7-dbde79739ab7",
  "event_type": "ping",
  "status": "pending",
  "attempts": 0,
  "next_attempt_at": "2026-11-07T14:31:44.554104Z",
  "last_response_code": null,
  "last_response_body": null,
  "last_error": null,
  "created_at": "2026-11-07T14:31:44.548818Z",
  "updated_at": null
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string (uuid) | yes | Unique ID. |
| `event_id` | string (uuid) | yes |  |
| `endpoint_id` | string (uuid) | yes |  |
| `event_type` | string | yes |  |
| `status` | string | yes |  |
| `attempts` | integer | yes |  |
| `next_attempt_at` | string (date-time), nullable | no |  |
| `last_response_code` | integer, nullable | no |  |
| `last_response_body` | string, nullable | no |  |
| `last_error` | string, nullable | no |  |
| `created_at` | string (date-time), nullable | no | When it was created (ISO 8601, UTC). |
| `updated_at` | string (date-time), nullable | no | When it was last changed (ISO 8601, UTC). |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `webhooks`.

  Example:

  ```json
  {"detail": "API key is missing required scope: webhooks"}
  ```

- `404` — Delivery not found, or it doesn't belong to your account.
- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```

