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

# POST /v1/webhooks — Register and Receive Event Webhooks

> POST /v1/webhooks — Registers a webhook endpoint to receive real-time campaign and reply events. Returns a webhook ID and registration status.

Webhooks allow your application or agent to receive real-time push notifications when key events occur inside Leadterra, eliminating the need to poll the API. When you register a webhook, you provide an HTTPS URL and a list of event types you want to subscribe to. Leadterra will send an HTTP `POST` request to that URL each time one of those events fires, delivering a structured JSON payload with the full event context. You can register multiple webhooks with different URLs and event sets to fan events out to several downstream systems simultaneously.

## Endpoint

```bash theme={null}
POST /v1/webhooks
```

## Authorization

All requests must include a valid API key in the `Authorization` header as a Bearer token.

```bash theme={null}
Authorization: Bearer sk_live_YOUR_KEY
```

## Request Body

<ParamField body="url" type="string" required>
  The HTTPS URL that Leadterra will `POST` event payloads to. Must use the `https://` scheme. Plain HTTP URLs are rejected to protect the integrity of event data in transit.
</ParamField>

<ParamField body="events" type="array" required>
  An array of event type strings that this webhook should subscribe to. At least one event must be specified. Valid values are:

  * `campaign.started` — fires when a campaign transitions to active and begins sending.
  * `message.sent` — fires each time an individual outbound email is successfully handed off to the mail provider.
  * `message.bounced` — fires when a sent message receives a hard or soft bounce notification.
  * `reply.received` — fires when an inbound reply is received and classified by Leadterra.
</ParamField>

## Example Request

```bash theme={null}
curl -X POST https://app.leadterra.co/v1/webhooks \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/leadterra",
    "events": [
      "campaign.started",
      "message.sent",
      "message.bounced",
      "reply.received"
    ]
  }'
```

## Response Fields

<ResponseField name="id" type="string">
  The unique identifier assigned to this webhook registration. Store this value if you need to reference or delete the webhook later.
</ResponseField>

<ResponseField name="url" type="string">
  The HTTPS URL you registered, echoed back for confirmation.
</ResponseField>

<ResponseField name="events" type="array">
  The list of event type strings this webhook is subscribed to, echoed back for confirmation.
</ResponseField>

<ResponseField name="status" type="string">
  The current status of the webhook. A newly registered webhook always returns `active`.
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO 8601 timestamp indicating when the webhook was registered.
</ResponseField>

## Example Response

```json theme={null}
{
  "id": "wh_456",
  "url": "https://your-app.com/webhooks/leadterra",
  "events": [
    "campaign.started",
    "message.sent",
    "message.bounced",
    "reply.received"
  ],
  "status": "active",
  "createdAt": "2025-06-10T12:00:00Z"
}
```

## Webhook Payload Structure

Every event Leadterra delivers to your endpoint shares a common envelope with an `event` type field, a `timestamp`, and an `data` object whose shape depends on the event type. Below is a sample payload for the `reply.received` event — the most commonly used event in agent-driven workflows.

```json theme={null}
{
  "event": "reply.received",
  "timestamp": "2025-06-10T14:22:00Z",
  "data": {
    "replyId": "rpl_001",
    "campaignId": "camp_123",
    "email": "ava@example.com",
    "classification": "interested",
    "threadSnippet": "Hi, this looks really interesting — can you send over more details on pricing?",
    "receivedAt": "2025-06-10T14:22:00Z"
  }
}
```

Your endpoint must return an HTTP `2xx` status code within **10 seconds** to acknowledge receipt. Any non-`2xx` response or a timeout is treated as a delivery failure and triggers the retry schedule described below.

## Retry Behavior

If Leadterra does not receive a `2xx` response from your endpoint, the delivery is considered failed and will be retried automatically using **exponential backoff**. The retry schedule is as follows:

| Attempt   | Delay after previous attempt |
| --------- | ---------------------------- |
| 1st retry | 30 seconds                   |
| 2nd retry | 5 minutes                    |
| 3rd retry | 30 minutes                   |
| 4th retry | 2 hours                      |
| 5th retry | 8 hours                      |

After five failed attempts, the event is marked as permanently undelivered and no further retries are made. Leadterra does not currently emit an alert when a webhook enters a persistent failure state, so we recommend monitoring your endpoint's availability and inspecting delivery logs in the dashboard under **Settings → Webhooks**.

To prevent duplicate processing on your side, ensure your event handler is idempotent — use the `replyId`, `campaignId`, or other stable identifiers in the payload to detect and discard re-delivered events.

<Tip>
  **Drive agent workflows with webhook events.** Registering a `reply.received` webhook is the most effective way to trigger an AI agent the moment a lead expresses interest. When your endpoint receives a payload with `classification: "interested"`, you can pass the `threadSnippet` and lead context directly into your agent's prompt, then use the Leadterra API to enrich the lead record or pause the sequence while a human or agent follows up — all without polling.
</Tip>

## Error Codes

| Code  | Meaning                                                                                                                    |
| ----- | -------------------------------------------------------------------------------------------------------------------------- |
| `400` | Bad Request — the request body is malformed, required fields are missing, or the `url` does not use the `https://` scheme. |
| `401` | Unauthorized — your API key is missing, invalid, or revoked.                                                               |
| `404` | Not Found — the requested resource could not be located.                                                                   |
| `422` | Unprocessable Entity — one or more `events` values are not recognized event types.                                         |
