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

# Custom Webhooks

> Programmatic HTTP integration for triggering calls and receiving results.

Custom Webhooks give developers full control over the Atllas integration layer. You can trigger calls via HTTP POST and receive call results delivered to any endpoint you specify.

<Note>
  Custom Webhooks are intended for developers and technical users. If you'd prefer a no-code solution, use [Zapier — Inbound](/docs/integrations/zapier-inbound) to trigger calls and [Zapier — Outbound](/docs/integrations/zapier-outbound) to receive results instead.
</Note>

## Inbound Webhooks — Trigger a Call

Inbound webhooks let external systems trigger AI calls on-demand. When your CRM (or any system) sends a request to your inbound webhook URL, Atllas initiates a call automatically.

### Setting Up

1. Go to **Integrations → Webhooks** and click **Inbound**
2. Select **Custom Webhook** to create this type of webhook
3. A webhook is created with a unique slug
4. Click **Create Campaign** to configure the AI script
5. Set up your prompt and test it
6. Use the webhook URL to trigger calls from your CRM

### Endpoint

```
POST https://wh.atll.as/{your-webhook-slug}
```

Each inbound webhook is bound to a specific campaign at creation time, so the campaign does not need to be specified per request.

### Request Body

```json theme={null}
{
  "type": "calls.execute",
  "data": {
    "phoneNumber": "+15551234567",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "companyName": "Acme Corp",
    "note": "Expressed interest in the enterprise plan"
  }
}
```

| Field                                                                       | Required | Description                                   |
| --------------------------------------------------------------------------- | -------- | --------------------------------------------- |
| `type`                                                                      | Yes      | Must be `"calls.execute"`                     |
| `data.phoneNumber`                                                          | Yes      | E.164 format recommended (`+15551234567`)     |
| `data.firstName`                                                            | No       | Used for personalisation                      |
| `data.lastName`                                                             | No       |                                               |
| `data.email`                                                                | No       | Used for email follow-ups if configured       |
| `data.companyName`                                                          | No       |                                               |
| `data.note`                                                                 | No       | Additional context for this contact           |
| `data.address1`, `data.address2`, `data.city`, `data.state`, `data.zipCode` | No       | Address fields, available for personalisation |

<Note>
  Field names are camelCase. Sending snake\_case (e.g. `first_name`, `phone_number`) will be ignored — the call will be placed using only `phoneNumber`.
</Note>

### Response

```json theme={null}
{ "success": true }
```

A `201` response means the request was accepted. The campaign attached to the webhook must be active for the call to actually be placed; if the user is out of credits, the call is recorded as cancelled even though the response status is the same.

### Customising the Phone Call

The webhook is bound to an **inbound campaign** in AI Calling. That campaign owns every call setting — voice, call script, voicemail message, follow-ups, call forwarding, keypad navigation, language, background ambiance, and so on. Whatever's on the campaign is what every webhook-triggered call uses.

To change call behaviour:

1. Open **AI Calling** and find the campaign your webhook is bound to (it'll be labelled as inbound)
2. Edit any setting — voice, script, follow-ups, voicemail, etc.
3. Save

Inbound campaigns can be edited at any time (unlike outbound campaigns, which lock once they start dialing). Changes apply to all future calls triggered by the webhook — already-completed calls keep the settings they ran with.

### Viewing Results

Every call triggered by the webhook appears in the bound campaign's results table in **AI Calling** — the same place outbound campaign results live.

Each call result includes the transcript, recording, AI summary, lead warmth classification, and extracted action items. See [Call Results & Transcripts](/docs/ai-calling/managing-campaigns/call-results-and-transcripts) for what's in the detail panel.

If you also want each completed call delivered to your own endpoint as it happens, configure a [Response Webhook](#response-webhooks) on the inbound webhook (covered below) — Atllas will POST the result back to your URL when the call wraps up.

***

## Outbound Webhooks — Receive Call Results

Outbound webhooks broadcast **every call completion event** to your configured endpoint(s). Whenever a call finishes — whether it came from a manual campaign, an inbound webhook trigger, or a test call — Atllas POSTs the results to every outbound webhook URL you've configured.

<Note>
  Creating an outbound webhook manually is only for Custom Webhooks. If you want to send call results to Zapier, set up an [outbound Zapier integration](/docs/integrations/zapier-outbound) instead — its outbound webhook is created automatically and labelled **Managed by Zapier**.
</Note>

### Setting Up

1. Go to **Integrations** and click **Outbound**
2. Enter your endpoint URL
3. Select HTTP method (POST or GET)
4. Optionally add custom headers for authentication (see [Custom Headers](#custom-headers))
5. Click **Test** to verify connectivity, then save

### Event

Outbound webhooks emit a single event:

| Event           | When                                                                                             |
| --------------- | ------------------------------------------------------------------------------------------------ |
| `call.finished` | Fires after the call completes and final analysis is attached (transcript, summary, lead warmth) |

### Payload

```json theme={null}
{
  "type": "call.finished",
  "timestamp": 1730179200000,
  "webhook": {
    "id": "webhook_abc123",
    "type": "outbound"
  },
  "call": {
    "id": "call_xyz789",
    "status": "finished",
    "recipient": {
      "phoneNumber": "+15551234567",
      "firstName": "Jane",
      "lastName": "Smith",
      "email": "jane@example.com",
      "address1": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "zipCode": "78701",
      "note": "VIP lead"
    },
    "summary": "Jane expressed interest in the enterprise plan and asked about pricing.",
    "transcript": [
      { "who": "assistant", "what": "Hi, is this Jane?", "when": "2024-04-30T14:00:03.000Z" },
      { "who": "user", "what": "Yes, speaking.", "when": "2024-04-30T14:00:05.000Z" }
    ],
    "recording": "https://...",
    "duration": 142,
    "analysis": {
      "sentiment": "positive",
      "lead_warmth": "hot",
      "justification": "Expressed immediate interest and asked specific pricing questions."
    },
    "tokensConsumed": 1
  },
  "campaign": {
    "id": "campaign_123",
    "name": "Q4 Outreach"
  }
}
```

### Field Reference

| Path                                                                           | Description                                                                                                                  |
| ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `type`                                                                         | Always `"call.finished"`                                                                                                     |
| `timestamp`                                                                    | Unix epoch in milliseconds                                                                                                   |
| `webhook.id`                                                                   | The outbound webhook's ID                                                                                                    |
| `webhook.type`                                                                 | Always `"outbound"`                                                                                                          |
| `call.id`                                                                      | The call ID                                                                                                                  |
| `call.status`                                                                  | One of: `finished`, `missed`, `failed`                                                                                       |
| `call.recipient.phoneNumber`                                                   | Contact phone in E.164                                                                                                       |
| `call.recipient.firstName` / `lastName` / `email`                              | Contact identity fields                                                                                                      |
| `call.recipient.address1` / `address2` / `city` / `state` / `zipCode` / `note` | Optional address + note                                                                                                      |
| `call.summary`                                                                 | One-paragraph AI summary                                                                                                     |
| `call.transcript`                                                              | Array of `{ who, what, when }` turns. `who` is typically `"user"` (caller) or `"assistant"` (AI); `when` is an ISO timestamp |
| `call.recording`                                                               | URL to the MP3 recording                                                                                                     |
| `call.duration`                                                                | Call length in seconds                                                                                                       |
| `call.analysis.sentiment`                                                      | `positive`, `neutral`, or `negative`                                                                                         |
| `call.analysis.lead_warmth`                                                    | `hot`, `warm`, `cold`, `dead`, or `unknown`                                                                                  |
| `call.analysis.justification`                                                  | AI's reasoning for the warmth classification                                                                                 |
| `call.tokensConsumed`                                                          | Credits used for the call                                                                                                    |
| `campaign.id` / `campaign.name`                                                | Source campaign info                                                                                                         |

### Status Values

| Status     | Meaning                                      |
| ---------- | -------------------------------------------- |
| `finished` | Call completed successfully                  |
| `missed`   | Recipient didn't answer or went to voicemail |
| `failed`   | Technical error occurred                     |

### Lead Warmth Values

| Value     | Meaning                              |
| --------- | ------------------------------------ |
| `hot`     | High interest, ready to move forward |
| `warm`    | Some interest, needs nurturing       |
| `cold`    | Low interest or not qualified        |
| `dead`    | No interest or wrong number          |
| `unknown` | Unable to determine                  |

### Sentiment Values

| Value      | Meaning                           |
| ---------- | --------------------------------- |
| `positive` | Friendly, receptive conversation  |
| `neutral`  | Professional, factual interaction |
| `negative` | Frustrated or hostile             |

<Note>
  Recipient field names are camelCase (`firstName`, `phoneNumber`) and there is no `company` field on the recipient. Build your integrations against the names shown above.
</Note>

***

## Response Webhooks

Response webhooks are configured on inbound webhooks to send call results back to a specific endpoint after an inbound-triggered call completes — forming a request/response pattern.

### Setting Up

1. Click **Configure** on your inbound webhook
2. Scroll to **Response Webhook (Optional)**
3. Enter your callback URL
4. Optionally add custom headers

The response webhook fires with `type: "call.completed"` and the same overall payload shape as outbound webhooks — with one difference: `recipient`, `summary`, `transcript`, `recording`, `duration`, `analysis`, and `tokensConsumed` are all optional, since some calls (e.g. ones that never connected) won't have all fields populated. Note that outbound webhooks use `type: "call.finished"`, not `call.completed`.

***

## Custom Headers

Custom headers let you authenticate against CRMs or other systems that require API keys or tokens.

### Adding Headers

1. When creating or editing a webhook, expand **Custom Headers (Optional)**
2. Click **Add Header**
3. Enter header name and value (e.g. `Authorization`, `Bearer token123`)
4. Headers are encrypted at rest and never exposed in the portal after saving

### FollowUp Boss Example

FollowUp Boss requires these headers:

```
X-System: your-app-name
X-System-Key: your-api-key
Authorization: Basic base64(api-key:)
```

Add them as:

1. `X-System` → `atllas-integration`
2. `X-System-Key` → `your-followupboss-api-key`
3. `Authorization` → `Basic {base64-encoded-key}`

### HubSpot Example

HubSpot workflow webhooks typically don't require custom headers — just configure the webhook URL in your HubSpot workflow.

For HubSpot API endpoints, add:

```
Authorization: Bearer your-access-token
```

***

## Testing & Logs

### Test Button

Each webhook has a **Test** button that sends a `call.completed`-shaped payload with example data:

```json theme={null}
{
  "type": "call.completed",
  "timestamp": 1730179200000,
  "webhook": { "id": "webhook_test_outbound_456", "type": "outbound" },
  "call": {
    "id": "call_test_abc123def456",
    "status": "finished",
    "recipient": {
      "phoneNumber": "+15555551234",
      "firstName": "Jessica",
      "lastName": "Smith",
      "email": "jessica.smith@example.com"
    },
    "summary": "Example call summary from the test button.",
    "transcript": [
      { "who": "assistant", "what": "Hi Jessica, this is Alex.", "when": "2024-04-30T14:00:03.000Z" },
      { "who": "user", "what": "I'm doing well, thanks for calling!", "when": "2024-04-30T14:00:05.000Z" }
    ],
    "recording": "https://storage.googleapis.com/atllas-call-recordings/test-recording-example.mp3",
    "duration": 123,
    "analysis": { "sentiment": "positive", "lead_warmth": "hot", "justification": "Example justification." },
    "tokensConsumed": 42
  },
  "campaign": { "id": "campaign_test_xyz789", "name": "Example Campaign" }
}
```

The test validates:

* The URL is reachable from our servers
* Custom headers are configured correctly
* Your endpoint responds with a 2xx status

<Warning>
  The test payload uses `call.completed` as the event type, while real outbound webhook deliveries use `call.finished`. If your endpoint routes behaviour by event type, handle both before relying on it in production.
</Warning>

### Delivery Logs

The delivery log shows recent webhook delivery attempts with:

* Timestamp
* HTTP status code returned by your endpoint
* Response body (truncated)
* Whether delivery succeeded or failed

Look for these status code ranges when debugging:

* `200–299`: Success
* `400–499`: Client error (check your endpoint)
* `500–599`: Server error (your endpoint is down)
* `errored`: Network/timeout failure

***

## Troubleshooting

### Webhook Not Firing

**For inbound webhooks:**

* Verify the campaign is created (click **Create Campaign**)
* Check the campaign has a prompt configured
* Ensure you're sending the correct payload format (`calls.execute`)
* Check webhook logs for incoming requests

**For outbound webhooks:**

* Verify the URL is correct and reachable
* Use the **Test** button to verify connectivity
* Check delivery logs for error messages

### Authentication Errors

If receiving 401 or 403 errors:

* Verify custom headers are correct
* Check that the API key hasn't expired
* Test with a simple endpoint (such as `webhook.site`) first
* Review the destination system's authentication requirements

### Call Not Triggering from Inbound Webhook

* Ensure campaign status is not `executing` or `completed`
* Check you have available calling credits
* Verify the phone number is in correct format (`+1...`)
* Check webhook logs for "insufficient funds" errors

***

## Security Best Practices

1. **Keep webhook URLs secret** — treat them like API keys
2. **Use HTTPS only** — never use HTTP endpoints
3. **Monitor delivery logs** — watch for unusual activity
4. **Rotate headers periodically** — update API keys regularly
5. **Test in staging first** — use the **Test** button before production
