# Webhook endpoints

> Add, list and remove webhook endpoints from your own code, so automation tools such as n8n and Zapier can subscribe to events by themselves. The same endpoints you manage in Developers.

Source: https://railbed.com/docs/api/webhook-endpoints/ · Updated: 2026-10-12 · Railbed by MediaGlobe developer docs

For AI assistants: the index of every docs page, with the rules for an integration, is https://railbed.com/docs/llms.txt

## The webhook endpoint object

A webhook endpoint is an address that receives your [events](https://railbed.com/docs/webhooks/events.md). Endpoints you add here are the same ones you see and manage in [Developers](https://app.railbed.io/developers), with the same [rules](https://railbed.com/docs/webhooks.md#add-an-endpoint): each mode has its own endpoints, up to 10 of them, each at a different address.

| Field | Type | Description |
|---|---|---|
| `id` | string | The endpoint's id, `we_…` |
| `object` | string | `"webhook_endpoint"` |
| `url` | string | Where events are sent |
| `description` | string or null | Your label for it, up to 80 characters |
| `events` | array of strings | The [event types](https://railbed.com/docs/webhooks/events.md) it receives |
| `livemode` | boolean | `false` for Test mode, `true` for Live mode |
| `mode` | string | `"test"` or `"live"`: the same fact as `livemode`, in words |
| `previous_secret_expires_at` | integer or null | After someone rolls the secret in Developers, when the previous secret stops signing, in Unix seconds. `null` the rest of the time |
| `created` | integer | When it was added, in Unix seconds |
| `secret` | string | The signing secret, `whsec_…`. **Only in the response to [Create](#create-a-webhook-endpoint)** |

Example: A webhook endpoint · response 200 OK

```json
{
  "id": "we_N8nQ4vRx7KtZ2mLp9WcB",
  "object": "webhook_endpoint",
  "url": "https://automation.example.com/webhook/railbed-orders",
  "description": "n8n: Fulfil paid orders",
  "events": [
    "payment.paid",
    "payment.held"
  ],
  "livemode": false,
  "mode": "test",
  "previous_secret_expires_at": null,
  "created": 1790380525
}
```

> [!IMPORTANT]
> The signing secret is returned once, when you create the endpoint. Store it where your receiver can read it, and [verify every delivery](https://railbed.com/docs/webhooks/signatures.md) with it. If you lose it, an owner or developer can reveal it in Developers, or you can delete the endpoint and create a new one.

## Create a webhook endpoint

`POST /v1/webhook_endpoints`

Adds an endpoint in the key's mode and returns it with its signing secret. Events start arriving with the next payment change.

| Parameter | Type | Description |
|---|---|---|
| `url` | string | Required. A full `https://` address with no username or password, up to 2,048 characters. Live endpoints must be on the public internet. Test endpoints also accept `http://localhost`, though Railbed's servers can't reach your computer that way |
| `events` | array of strings | The [event types](https://railbed.com/docs/webhooks/events.md) to receive, at least one. Default: every type |
| `description` | string | Optional label, up to 80 characters, shown on the endpoint in Developers. Name the tool and the job, such as `n8n: Fulfil paid orders` |

Example: cURL

```bash
curl https://pay.railbed.io/v1/webhook_endpoints \
  -H "Authorization: Bearer $RAILBED_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://automation.example.com/webhook/railbed-orders",
    "description": "n8n: Fulfil paid orders",
    "events": ["payment.paid", "payment.held"]
  }'
```

Example: Node\.js

```js
const endpoint = await fetch('https://pay.railbed.io/v1/webhook_endpoints', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.RAILBED_SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://automation.example.com/webhook/railbed-orders',
    description: 'n8n: Fulfil paid orders',
    events: ['payment.paid', 'payment.held'],
  }),
}).then((r) => r.json());
// Save endpoint.id and endpoint.secret now: the secret isn't returned again.
```

Example: Python

```python
endpoint = requests.post(
    "https://pay.railbed.io/v1/webhook_endpoints",
    headers={"Authorization": f"Bearer {os.environ['RAILBED_SECRET_KEY']}"},
    json={
        "url": "https://automation.example.com/webhook/railbed-orders",
        "description": "n8n: Fulfil paid orders",
        "events": ["payment.paid", "payment.held"],
    },
    timeout=15,
).json()
# Save endpoint["id"] and endpoint["secret"] now: the secret isn't returned again.
```

Example: Response · response 201 Created

```json
{
  "id": "we_N8nQ4vRx7KtZ2mLp9WcB",
  "object": "webhook_endpoint",
  "url": "https://automation.example.com/webhook/railbed-orders",
  "description": "n8n: Fulfil paid orders",
  "events": [
    "payment.paid",
    "payment.held"
  ],
  "livemode": false,
  "mode": "test",
  "previous_secret_expires_at": null,
  "created": 1790380525,
  "secret": "whsec_Tq7mVx2RkL9pZc4WbN8yHd3Jf6Gs1Ae0Ku5Xo-Pi_Mr"
}
```

The endpoint appears in Developers straight away, marked as added with your key, and your account's activity names the key that added it. In Live mode, the account's owners and developers also get an email saying which key added an endpoint and where it sends your events, so a key that leaked can't quietly copy your events somewhere else.

Each endpoint stays tied to the key that added it: [revoking that key](https://railbed.com/docs/api.md#secret-keys) removes it too.

| Error | Cause |
|---|---|
| `400 invalid_url` | The address isn't a full `https://` address, has a username or password, is over 2,048 characters, or (in Live) isn't on the public internet. `field` is `url` |
| `400 invalid_events` | `events` is empty or names a type that doesn't exist. `field` is `events` |
| `400 invalid_description` | `description` isn't text |
| `400 too_long` | `description` is over 80 characters |
| `409 duplicate_url` | That address is already one of your endpoints in this mode. Delete that endpoint first, then add it again (see [For automation tools](#automation-tools)) |
| `409 too_many` | This mode already has 10 endpoints. Remove one first |
| `403 merchant_read_only` | The account is suspended or archived |

## Retrieve a webhook endpoint

`GET /v1/webhook_endpoints/:id`

Returns one of your endpoints in the key's mode, without its secret. A removed endpoint, another account's, or one in the other mode is `404 not_found`, so an automation tool can tell whether its subscription still exists.

Example: cURL

```bash
curl https://pay.railbed.io/v1/webhook_endpoints/we_N8nQ4vRx7KtZ2mLp9WcB \
  -H "Authorization: Bearer $RAILBED_SECRET_KEY"
```

## List webhook endpoints

`GET /v1/webhook_endpoints`

Returns your endpoints in the key's mode, newest first, without their secrets. Removed endpoints aren't listed.

| Parameter | Type | Description |
|---|---|---|
| `limit` | integer | 1–100. Default 20 |
| `starting_after` | string | An endpoint id from the previous page's `next_cursor` |

Example: cURL

```bash
curl https://pay.railbed.io/v1/webhook_endpoints \
  -H "Authorization: Bearer $RAILBED_SECRET_KEY"
```

Example: Response · response 200 OK

```json
{
  "data": [
    {
      "id": "we_N8nQ4vRx7KtZ2mLp9WcB",
      "object": "webhook_endpoint",
      "url": "https://automation.example.com/webhook/railbed-orders",
      "description": "n8n: Fulfil paid orders",
      "events": [
        "payment.paid",
        "payment.held"
      ],
      "livemode": false,
      "mode": "test",
      "previous_secret_expires_at": null,
      "created": 1790380525
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Delete a webhook endpoint

`DELETE /v1/webhook_endpoints/:id`

Removes the endpoint: it stops receiving events, its pending retries stop and its signing secret no longer exists. Its delivery history stays readable under **Removed endpoints** in Developers for 30 days. Deleting works even when the account is suspended or archived, as removing an endpoint in the dashboard does.

Example: cURL

```bash
curl -X DELETE https://pay.railbed.io/v1/webhook_endpoints/we_N8nQ4vRx7KtZ2mLp9WcB \
  -H "Authorization: Bearer $RAILBED_SECRET_KEY"
```

Example: Response · response 200 OK

```json
{
  "id": "we_N8nQ4vRx7KtZ2mLp9WcB",
  "object": "webhook_endpoint",
  "deleted": true
}
```

Deleting an endpoint that was already removed is `404 not_found`; treat that as done.

## Access

These endpoints need the `webhooks:read` (retrieve, list) and `webhooks:write` (create, delete) [scopes](https://railbed.com/docs/api.md#scopes). Secret keys for the API with **Full access** have both. **Read only** keys have neither, because an endpoint's address can carry its receiver's own token. WooCommerce keys and connected AI agents never have them: use a secret key made for the API.

## For automation tools

A tool that subscribes on a user's behalf (an n8n trigger, a Zapier REST hook, your own job runner) follows the same pattern:

1. When the user turns the automation on, create an endpoint with the tool's receiving address, the events it needs and a description naming the tool and the automation. Store the returned `id` and `secret` with the automation.
2. Verify every delivery's `Railbed-Signature` with that secret, as in [Verify signatures](https://railbed.com/docs/webhooks/signatures.md), and answer `2xx` quickly.
3. Before reusing a stored subscription, retrieve it by `id`. A `404` means it was removed (in Developers, by another tool, or because its key was revoked): create a new one.
4. If creating answers `409 duplicate_url`, your tool lost track of an endpoint it made earlier, and its secret can't be read back. List your endpoints, delete the one whose `url` is your receiving address, and create it again.
5. When the user turns the automation off, delete the endpoint. A `404` means it's already gone.
6. Once your receiving address answers `410 Gone` to a delivery, Railbed removes that endpoint instead of retrying, as it would for a tool that unsubscribed. This applies only to endpoints added through this API; endpoints someone added in Developers keep retrying.

> [!NOTE]
> Rolling an endpoint's secret in Developers gives it a new secret that the API doesn't return. A tool keeps working for the 24-hour overlap and then fails verification. To recover, turn the automation off and on again, which creates a fresh endpoint with a new secret.

Every endpoint counts toward the 10 per mode, including ones a tool forgot to delete. Remove stale ones in Developers, or list them here and delete the ones your tool created (its `description` tells you which).
