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

# Custom Webhooks

> Trigger an agent from a custom JSON webhook

Custom Webhooks let you trigger a Duckie agent from any system that can send a JSON `POST` request. Use them when your source system is not one of Duckie's built-in deployment channels, or when you need to shape a custom event payload into Duckie runs.

## How Custom Webhooks Work

A Custom Webhook deployment has two parts:

| Part                 | What it controls                                                                 |
| -------------------- | -------------------------------------------------------------------------------- |
| **Webhook endpoint** | The generated URL, endpoint name, and request verification method                |
| **Payload mappings** | How fields from the incoming JSON payload become the run, messages, and metadata |

When Duckie receives a valid request, it checks the deployment's event filtering, extracts the mapped fields, creates a run, inserts the mapped run messages, and queues the selected agent.

## Create a Custom Webhook Deployment

<Steps>
  <Step title="Create a deployment">
    Go to **Deploy**, click **Create Deployment**, and choose the agent that should handle the webhook.
  </Step>

  <Step title="Choose Custom Webhook">
    In the trigger selector, choose **Custom Webhook**.
  </Step>

  <Step title="Name the endpoint">
    Enter an **Endpoint Name**. This name identifies the webhook endpoint in Duckie.
  </Step>

  <Step title="Choose a verification method">
    Select how Duckie should verify incoming requests.
  </Step>

  <Step title="Map the payload">
    Paste a sample JSON payload, parse the fields, and map the payload into run fields, run messages, and optional metadata.
  </Step>

  <Step title="Save and copy the webhook URL">
    Save the deployment. Duckie shows the generated webhook URL after the endpoint is created.
  </Step>
</Steps>

## Webhook URL

After you save the deployment for the first time, Duckie shows a generated URL:

```text theme={null}
https://app.useduckie.ai/api/webhooks/custom/{endpointSlug}
```

Send JSON `POST` requests to this URL. Duckie reads the request body as JSON, so non-JSON payloads are rejected.

## Verification Methods

Choose a verification method before sending production traffic.

| Method             | How it works                                                                                                      |
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| **HMAC Signature** | Duckie computes an HMAC over the raw request body with your signing secret and compares it to a signature header. |
| **Bearer Token**   | Duckie checks that the configured header value exactly matches the token you entered.                             |
| **None**           | Duckie skips request verification. Use only for local testing or trusted temporary traffic.                       |

For HMAC verification, configure the signature header and algorithm. Duckie supports the algorithms available in the selector, including SHA-256, SHA-1, and SHA-512.

For token verification, the default header is `X-Webhook-Token`. Send the token as the header value:

```bash theme={null}
curl -X POST "https://app.useduckie.ai/api/webhooks/custom/{endpointSlug}" \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Token: YOUR_TOKEN" \
  -d '{"id":"evt_123","ticket_id":"T-123","title":"Billing question","message":"Can you help me update my invoice?"}'
```

## Payload Mappings

Payload mappings tell Duckie how to turn your JSON payload into a run.

Most mapping fields accept either:

| Mapping type | Example                                    |
| ------------ | ------------------------------------------ |
| **JSONPath** | `$.ticket.id`                              |
| **Template** | `Ticket {{ticket.id}}: {{ticket.subject}}` |

JSONPath reads a value from the payload. Templates combine literal text with dot-path placeholders from the payload.

### Parse a Sample Payload

Paste a sample JSON payload into **Sample JSON Payload** and click **Parse Fields**. Duckie discovers scalar fields, suggests common mappings such as IDs and messages, and makes discovered fields available in mapping dropdowns.

The parser accepts normal JSON, fenced JSON snippets, and common pasted object-literal formatting such as single quotes, unquoted keys, and trailing commas.

### Run Fields

Run fields are required fields written to the run.

| Field                 | Required | Description                                                                       |
| --------------------- | -------- | --------------------------------------------------------------------------------- |
| **Event ID**          | Yes      | Unique ID for this webhook event.                                                 |
| **Ticket ID**         | Yes      | Stable ID for the customer issue, conversation, ticket, or object.                |
| **Ticket Title**      | Yes      | Short title shown in run history.                                                 |
| **Ticket URL**        | No       | Link back to the source system.                                                   |
| **Source Message ID** | No       | Source message ID used for tracking. If omitted, Duckie uses the mapped Event ID. |

Example mappings:

```json theme={null}
{
  "event_id": "$.event.id",
  "ticket_id": "$.ticket.id",
  "ticket_title": "Ticket {{ticket.id}}: {{ticket.subject}}",
  "ticket_url": "https://example.com/tickets/{{ticket.id}}",
  "source_message_id": "$.message.id"
}
```

### Run Messages

Run messages provide the conversation content the agent should respond to.

Use **Single message** when each webhook payload contains one new message:

| Field                 | Required | Description                                                         |
| --------------------- | -------- | ------------------------------------------------------------------- |
| **Message Body**      | Yes      | The text the agent should process.                                  |
| **Role**              | No       | Fixed role or a role mapped from the payload. Defaults to customer. |
| **User Name**         | No       | Name shown for the message author. Defaults to `Customer`.          |
| **Source Message ID** | No       | Message ID from the source system.                                  |
| **Timestamp**         | No       | Message timestamp from the source system.                           |

Use **Conversation array** when the payload contains an array of messages or comments. Set **Array Path** to the array, then map message fields relative to each item in that array.

Example payload:

```json theme={null}
{
  "event": { "id": "evt_123", "type": "ticket.created" },
  "ticket": { "id": "T-123", "subject": "Billing question" },
  "comments": [
    {
      "id": "c_1",
      "body": "Can you help me update my invoice?",
      "sender_type": "end_user",
      "sender_name": "Alice",
      "created_at": "2026-05-29T16:30:00Z"
    },
    {
      "id": "c_2",
      "body": "Sure, I can help.",
      "sender_type": "admin",
      "sender_name": "Support",
      "created_at": "2026-05-29T16:31:00Z"
    }
  ]
}
```

Example conversation array mappings:

| Field                 | Mapping         |
| --------------------- | --------------- |
| **Array Path**        | `$.comments`    |
| **Message Body**      | `$.body`        |
| **Role**              | `$.sender_type` |
| **User Name**         | `$.sender_name` |
| **Source Message ID** | `$.id`          |
| **Timestamp**         | `$.created_at`  |

When role is mapped from the payload, Duckie normalizes common values:

| Payload values                                      | Duckie role |
| --------------------------------------------------- | ----------- |
| `customer`, `user`, `end_user`, `contact`           | Customer    |
| `agent`, `admin`, `support`, `teammate`, `operator` | Agent       |
| `duckie`, `assistant`, `bot`                        | Duckie      |

If the mapped role is missing or unrecognized, Duckie uses the fallback role selected in the deployment.

### Run Metadata

Run Metadata stores optional fields on the run for context. Use it for source-specific attributes such as account ID, priority, product area, locale, plan, or tags.

You can add fields manually, or click **Add Parsed Fields** after parsing a sample payload.

<Warning>
  Do not send secrets, credentials, or unnecessary sensitive fields in webhook payloads. Duckie stores the raw payload and mapped metadata with the run.
</Warning>

## Event Filtering

Event filtering lets one webhook endpoint receive multiple event types while a deployment only processes the ones you allow.

| Field                   | Description                                                                                |
| ----------------------- | ------------------------------------------------------------------------------------------ |
| **Event Type Path**     | JSONPath to the field that identifies the event type, such as `$.event.type`.              |
| **Allowed Event Types** | Comma-separated list of exact event type values, such as `ticket.created, ticket.updated`. |

If you leave the allowed event types blank, Duckie accepts all event types for that deployment.

## Request Outcomes

Duckie validates the request before it starts asynchronous processing.

| Outcome                     | Meaning                                                           |
| --------------------------- | ----------------------------------------------------------------- |
| `200` with `{ "ok": true }` | The request was accepted and processing continues asynchronously. |
| `400`                       | The request body was not valid JSON.                              |
| `401`                       | Request verification failed.                                      |
| `404`                       | The webhook endpoint slug was not found.                          |
| `429`                       | The endpoint rate limit was exceeded.                             |

If filtering does not match, Duckie accepts the request but does not create a run for that deployment. If required mappings resolve to empty values, Duckie does not create a run for that deployment.

## Test Before Going Live

1. Create the deployment in **Testing** mode.
2. Send a representative sample payload to the webhook URL.
3. Open **Analyze > Runs** and review the created run.
4. Confirm the title, ticket ID, messages, role mapping, and metadata look correct.
5. Switch to **Live** mode after the mapping is validated.

## Troubleshooting

| Problem                    | What to check                                                                                                  |
| -------------------------- | -------------------------------------------------------------------------------------------------------------- |
| No run appears             | Confirm the deployment is active and the event type filter matches the payload.                                |
| Mapping failure            | Check that required mappings resolve to non-empty values: Event ID, Ticket ID, Ticket Title, and Message Body. |
| Wrong message role         | Check the role mapping and fallback role.                                                                      |
| `401 Unauthorized`         | Confirm the signature or token header name and secret match the deployment.                                    |
| `400 Invalid JSON payload` | Send a JSON request body with `Content-Type: application/json`.                                                |

## Next Steps

<CardGroup cols={2}>
  <Card title="Create Deployment" icon="plus" href="/deployments/creating-deployments">
    Set up a deployment
  </Card>

  <Card title="Deployment Modes" icon="toggle-on" href="/deployments/deployment-modes">
    Test before live traffic
  </Card>

  <Card title="Runs" icon="chart-line" href="/analytics/runs">
    Review webhook-triggered runs
  </Card>

  <Card title="Duckie as an External Subagent" icon="plug" href="/examples/duckie-as-external-subagent">
    Let a customer-owned orchestration agent call Duckie with a webhook.
  </Card>
</CardGroup>
