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

# Webhook Integration

> Set up inbound webhooks for receiving leads and messages from WhatsApp, Telegram, and Email.

Naturalead uses inbound webhooks to receive leads from external systems and messages from messaging channels. Each webhook type has its own authentication mechanism and setup process.

<CardGroup cols={2}>
  <Card title="Lead Webhooks" icon="user-plus">
    Token-based authentication. Push leads from any CRM or form tool into Naturalead.
  </Card>

  <Card title="WhatsApp (Meta)" icon="message-circle">
    HMAC-SHA256 signature verification via the Meta Cloud API.
  </Card>

  <Card title="Telegram" icon="send">
    Registered as a Telegram bot webhook URL via BotFather.
  </Card>

  <Card title="Email (Resend)" icon="mail">
    Inbound email events from Resend with conversation threading.
  </Card>
</CardGroup>

***

## Lead inbound webhook

The lead webhook lets you push leads into Naturalead from any external system (CRM, landing page, form builder, etc.) without API key authentication. Each account has a unique webhook token.

<Steps>
  ### Find your webhook token

  Your webhook token is available in the account settings. It is unique per account.

  <CodeGroup>
    ```bash curl theme={null}
    # Get your account details (includes webhookToken)
    curl "${API_URL}/api/accounts/current" \
      -H "X-API-Key: ${API_KEY}"
    ```
  </CodeGroup>

  ### Send leads to the webhook

  Post a JSON body with lead data to the webhook URL. The only required field is `phone`.

  <CodeGroup>
    ```bash curl theme={null}
    curl -X POST "${API_URL}/api/webhooks/leads/${WEBHOOK_TOKEN}" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Jane Doe",
        "phone": "+1234567890",
        "email": "jane@example.com",
        "tags": ["website", "high-intent"],
        "company": "Acme Corp",
        "source_page": "/pricing"
      }'
    ```

    ```python Python theme={null}
    import requests

    webhook_url = f"{API_URL}/api/webhooks/leads/{WEBHOOK_TOKEN}"

    lead = {
        "name": "Jane Doe",
        "phone": "+1234567890",
        "email": "jane@example.com",
        "tags": ["website", "high-intent"],
        "company": "Acme Corp",
        "source_page": "/pricing",
    }

    response = requests.post(webhook_url, json=lead)
    data = response.json()

    if data["created"]:
        print(f"New lead created: {data['leadId']}")
    else:
        print(f"Lead already exists: {data['leadId']}")
    ```

    ```javascript Node.js theme={null}
    const webhookUrl = `${API_URL}/api/webhooks/leads/${WEBHOOK_TOKEN}`;

    const response = await fetch(webhookUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        name: "Jane Doe",
        phone: "+1234567890",
        email: "jane@example.com",
        tags: ["website", "high-intent"],
        company: "Acme Corp",
        source_page: "/pricing",
      }),
    });

    const data = await response.json();
    console.log(data.created ? `New lead: ${data.leadId}` : `Existing lead: ${data.leadId}`);
    ```
  </CodeGroup>

  <Info>
    Leads are deduplicated by phone number. If a lead with the same phone already exists, the webhook returns `201` with `created: false` and the existing lead ID. Any additional string fields beyond `name`, `phone`, `email`, and `tags` are stored as custom fields.
  </Info>

  ### Handle the response

  | Status | Meaning                                 |
  | ------ | --------------------------------------- |
  | `201`  | New lead created                        |
  | `200`  | Lead already exists (deduplicated)      |
  | `400`  | Validation error (e.g. missing `phone`) |
  | `401`  | Invalid webhook token                   |
</Steps>

***

## WhatsApp webhook (Meta Cloud API)

The WhatsApp integration uses the Meta Cloud API. You need to configure your WABA (WhatsApp Business Account) in the Meta Developer Portal and point the webhook to Naturalead.

<Steps>
  ### Set environment variables

  Configure these environment variables on the Naturalead backend:

  | Variable                    | Description                                                |
  | --------------------------- | ---------------------------------------------------------- |
  | `META_WEBHOOK_VERIFY_TOKEN` | A secret string you choose for webhook verification        |
  | `FB_APP_SECRET`             | Your Facebook App Secret (used for signature verification) |
  | `FB_APP_ID`                 | Your Facebook App ID                                       |

  ### Register the webhook URL in Meta

  In the [Meta Developer Portal](https://developers.facebook.com/), navigate to your app's WhatsApp configuration and set the webhook URL:

  ```
  https://your-api-domain.com/api/webhooks/meta-whatsapp
  ```

  Enter the same verify token you set in `META_WEBHOOK_VERIFY_TOKEN`. Meta will send a GET request with a challenge that Naturalead echoes back to complete verification.

  ### Signature verification

  Every inbound POST from Meta includes an `X-Hub-Signature-256` header containing an HMAC-SHA256 hash of the request body, signed with your `FB_APP_SECRET`.

  Naturalead validates this signature automatically. Here is how the verification works internally:

  ```javascript theme={null}
  const crypto = require("crypto");

  function validateSignature(rawBody, signature, appSecret) {
    const expected =
      "sha256=" +
      crypto.createHmac("sha256", appSecret).update(rawBody).digest("hex");

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected)
    );
  }
  ```

  <Warning>
    If `FB_APP_SECRET` is not configured, signature validation is skipped (development mode only). Always set it in production.
  </Warning>

  ### Connect your WABA

  After the webhook is verified, connect your WhatsApp Business Account through the Naturalead dashboard settings page. The integration stores your WABA ID, phone number ID, and access token for sending outbound messages.
</Steps>

<Note>
  The WhatsApp webhook always returns HTTP 200, even on errors. This is required by Meta -- non-200 responses cause Meta to retry with exponential backoff and eventually disable the webhook.
</Note>

***

## Telegram webhook

The Telegram integration uses a bot token from BotFather. Naturalead registers itself as the bot's webhook endpoint.

<Steps>
  ### Create a Telegram bot

  1. Open Telegram and message [@BotFather](https://t.me/BotFather)
  2. Send `/newbot` and follow the prompts
  3. Copy the bot token (e.g. `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`)

  ### Configure in Naturalead

  <CodeGroup>
    ```bash curl theme={null}
    curl -X PUT "${API_URL}/api/integrations/telegram" \
      -H "X-API-Key: ${API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{ "telegramBotToken": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz" }'
    ```

    ```python Python theme={null}
    response = requests.put(
        f"{API_URL}/api/integrations/telegram",
        headers={
            "X-API-Key": API_KEY,
            "Content-Type": "application/json",
        },
        json={"telegramBotToken": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"},
    )

    data = response.json()
    print(f"Bot configured: @{data['botUsername']}")
    ```

    ```javascript Node.js theme={null}
    const response = await fetch(`${API_URL}/api/integrations/telegram`, {
      method: "PUT",
      headers: {
        "X-API-Key": API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        telegramBotToken: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
      }),
    });

    const data = await response.json();
    console.log(`Bot configured: @${data.botUsername}`);
    ```
  </CodeGroup>

  Naturalead verifies the token by calling the Telegram `getMe` API. If valid, the token is saved and the webhook URL is:

  ```
  https://your-api-domain.com/api/webhooks/telegram
  ```

  ### Register the webhook with Telegram

  Set the webhook URL using the Telegram Bot API:

  ```bash theme={null}
  curl "https://api.telegram.org/bot${BOT_TOKEN}/setWebhook?url=https://your-api-domain.com/api/webhooks/telegram"
  ```

  ### How it works

  When a user sends a text message to the bot, Telegram posts an Update object to the webhook. Naturalead:

  1. Parses the inbound message (non-text messages are ignored)
  2. Looks up the account by matching the stored bot token
  3. Routes the message to the conversation engine for LLM processing
  4. Sends the AI response back through the Telegram bot
</Steps>

***

## Email webhook (Resend)

The email integration uses Resend for both sending and receiving emails. Inbound emails are received via Resend webhook events.

<Steps>
  ### Configure email sending

  First, set up your sending email address and verify the domain:

  <CodeGroup>
    ```bash curl theme={null}
    # Step 1: Configure the from address
    curl -X PUT "${API_URL}/api/integrations/email" \
      -H "X-API-Key: ${API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{
        "emailFromAddress": "bot@yourcompany.com",
        "emailFromName": "Naturalead Bot"
      }'

    # Step 2: Add the DNS records returned in the response

    # Step 3: Verify the domain
    curl -X POST "${API_URL}/api/integrations/email/verify" \
      -H "X-API-Key: ${API_KEY}"
    ```
  </CodeGroup>

  The configure endpoint returns DNS records (SPF, DKIM, etc.) that must be added to your domain before verification will pass.

  ### Set up Resend inbound webhook

  1. In the [Resend dashboard](https://resend.com/), go to **Webhooks**
  2. Create a new webhook pointing to: `https://your-api-domain.com/api/webhooks/email`
  3. Subscribe to the `email.received` event

  ### Configure the receiving domain

  Set the `RESEND_RECEIVING_DOMAIN` environment variable to your inbound email domain. Naturalead uses the pattern `reply+{conversationId}@{domain}` for email threading, allowing it to match inbound replies to the correct conversation.

  ### How email threading works

  1. When Naturalead sends an outbound email, it sets the reply-to address to `reply+{conversationId}@{domain}`
  2. When the lead replies, the email is routed to the Resend webhook
  3. Naturalead extracts the conversation ID from the `to` address
  4. The full email content is fetched from the Resend API
  5. The message is routed to the conversation engine
</Steps>

***

## Webhook token management

<Info>
  Each Naturalead account has a unique `webhookToken` generated at account creation. This token is used exclusively for the lead inbound webhook and is separate from API keys.
</Info>

* The webhook token is visible via `GET /api/accounts/current` (requires `account:view` permission)
* Webhook tokens do not expire -- if compromised, contact support to regenerate
* All leads created via webhook are logged to the audit trail with `source: "webhook"`

## Troubleshooting

| Problem                             | Solution                                                                                                              |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Lead webhook returns 401            | Check that the token in the URL matches your account's `webhookToken`                                                 |
| WhatsApp messages not arriving      | Verify the webhook URL is registered in Meta Developer Portal and the `META_WEBHOOK_VERIFY_TOKEN` matches             |
| WhatsApp signature validation fails | Ensure `FB_APP_SECRET` is set correctly and the raw request body is preserved                                         |
| Telegram bot not responding         | Run `getWebhookInfo` to confirm the URL is registered: `curl https://api.telegram.org/bot${BOT_TOKEN}/getWebhookInfo` |
| Email replies not detected          | Check that `RESEND_RECEIVING_DOMAIN` matches your domain and DNS records are verified                                 |
| Email content is empty              | Verify the Resend webhook is subscribed to `email.received` events and `RESEND_API_KEY` is set                        |
