> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://natural.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://natural.ferndocs.com/_mcp/server.

# Webhooks

Natural sends real-time event notifications to your registered webhook endpoints using [Standard Webhooks](https://www.standardwebhooks.com/). This lets you react to payments, transfers, and other events without polling the API.

## Register an endpoint

Register a webhook endpoint via the dashboard or API:

```bash
curl -X POST https://api.natural.co/webhooks \
  -H "Authorization: Bearer $NATURAL_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "attributes": {
        "url": "https://your-app.com/webhooks/natural",
        "events": ["payment.completed", "payment.failed", "transfer.completed"]
      }
    }
  }'
```

## Verify signatures

All webhook deliveries include a signature in the `webhook-signature` header. Verify it using the Standard Webhooks library:

**`Python`**

```python title="Python"
from standardwebhooks import Webhook

wh = Webhook(signing_secret)
payload = wh.verify(request.body, request.headers)
```

**`TypeScript`**

```typescript title="TypeScript"
import { Webhook } from "standardwebhooks";

const wh = new Webhook(signingSecret);
const payload = wh.verify(request.body, request.headers);
```

## Event types

| Event                       | Description                          |
| --------------------------- | ------------------------------------ |
| `payment.created`           | A payment was initiated              |
| `payment.completed`         | A payment was successfully delivered |
| `payment.failed`            | A payment failed                     |
| `payment.cancelled`         | A payment was cancelled              |
| `transfer.completed`        | A deposit or withdrawal completed    |
| `transfer.failed`           | A deposit or withdrawal failed       |
| `payment_request.fulfilled` | A payment request was paid           |
| `payment_request.expired`   | A payment request expired            |

## Best practices

* **Respond quickly** — Return a `2xx` status within 5 seconds. Process events asynchronously if needed.
* **Handle duplicates** — Use the event ID for idempotency. Natural may retry delivery.
* **Verify signatures** — Always validate the `webhook-signature` header before processing.