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

# POST /vapi/webhook/{id} — Vapi assistant-request event

> Handles Vapi assistant-request events to return dynamic per-call LLM configuration, with the custom LLM URL rewritten to point at the Masker proxy.

Vapi calls this endpoint at the start of each call to retrieve the assistant configuration. It handles the `assistant-request` event type. Masker validates the request signature, looks up the agent, resolves the assistant configuration, rewrites the custom LLM URL to point at the Masker proxy, and returns the configuration to Vapi.

This endpoint is optional. If your Vapi assistant is fully static, you can skip it — just put the Masker proxy URL directly in your Vapi assistant's **Custom LLM URL** field. Use this webhook when you need to vary the system prompt per call, attach session metadata to the audit log, or override the model or tools dynamically.

<Note>
  This endpoint is specific to Vapi's assistant-request webhook format. For Bolna and Retell integrations, see their respective documentation — those platforms use a different webhook shape.
</Note>

## Endpoint

```
POST /vapi/webhook/{agent_id}
```

This endpoint sits outside the `/api/v1` namespace.

## Path parameters

<ParamField path="agent_id" type="string" required>
  Masker agent ID in `agt_*` ULID format.
</ParamField>

## Authentication

This endpoint is **not** authenticated by `masker_session` cookie. It validates the `X-Vapi-Signature` header using HMAC verification against `MASKER_VAPI_WEBHOOK_SECRET`.

Configure the same secret value on both sides:

* Vapi assistant → **Server URL Secret** field
* Masker environment → `MASKER_VAPI_WEBHOOK_SECRET` variable

A signature mismatch returns `401`.

## Request body

Vapi sends a standard assistant-request payload:

<ParamField body="type" type="string" required>
  Must be `assistant-request`. Other event types return `422 unsupported_event`.
</ParamField>

<ParamField body="call" type="object" required>
  Vapi call object.

  <Expandable title="call fields">
    <ParamField body="id" type="string">
      Vapi call ID. Masker logs this in the audit trail (no PHI).
    </ParamField>

    <ParamField body="customer" type="object">
      Customer information. The `number` field is masked before logging — Masker treats Vapi's payload like any other input.
    </ParamField>

    <ParamField body="assistantId" type="string">
      Vapi assistant ID.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="timestamp" type="number">
  Unix millisecond timestamp from Vapi.
</ParamField>

## Response fields

Masker returns a Vapi assistant configuration object with `model.url` rewritten to point at the Masker proxy for this agent:

<ResponseField name="assistant" type="object" required>
  Full Vapi assistant configuration.

  <Expandable title="assistant fields" defaultOpen>
    <ResponseField name="model" type="object" required>
      LLM model configuration. `provider` is always `custom-llm` and `url` always points to the Masker proxy.
    </ResponseField>

    <ResponseField name="voice" type="object">
      Voice provider configuration, passed through unchanged.
    </ResponseField>

    <ResponseField name="firstMessage" type="string">
      Opening message Vapi speaks at the start of the call.
    </ResponseField>

    <ResponseField name="transcriber" type="object">
      Transcription provider configuration, passed through unchanged.
    </ResponseField>
  </Expandable>
</ResponseField>

## Processing behavior

1. Validate the `X-Vapi-Signature` header.
2. Look up the agent by `agent_id`.
3. Resolve the per-call assistant configuration (static defaults, or dynamic overrides if a callback is configured on the agent).
4. Rewrite `model.url` to point at `https://masker-voice.fly.dev/proxy/{agent_id}/v1/chat/completions`.
5. Append a `call_started` event to the audit log with the Vapi `call.id`.
6. Return the assistant configuration to Vapi.

## What gets logged

Masker logs the Vapi `call.id`, the assistant ID, and the chosen model. The customer's phone number is masked before logging. The resolved system prompt, `firstMessage`, and any other config fields that might contain PHI are not logged.

## Example

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST \
    -H "Content-Type: application/json" \
    -H "X-Vapi-Signature: <hmac-sha256>" \
    https://masker-voice.fly.dev/vapi/webhook/agt_01HYZ... \
    -d '{
      "type": "assistant-request",
      "call": {
        "id": "call_01HYZ...",
        "customer": {"number": "+14155552671"},
        "assistantId": "asst_..."
      },
      "timestamp": 1714771872000
    }'
  ```
</CodeGroup>

```json Response theme={null}
{
  "assistant": {
    "model": {
      "provider": "custom-llm",
      "url": "https://masker-voice.fly.dev/proxy/agt_01HYZ.../v1/chat/completions",
      "model": "gpt-4o-mini"
    },
    "voice": {"provider": "11labs", "voiceId": "..."},
    "firstMessage": "Hi, thanks for calling. How can I help?",
    "transcriber": {"provider": "deepgram", "model": "nova-2"}
  }
}
```

## Rate limit

100 requests/second sustained, burst 200. Rate-limited requests receive `429` with a `Retry-After` header.

## Errors

| Status | Code                | Meaning                                                                             |
| ------ | ------------------- | ----------------------------------------------------------------------------------- |
| `401`  | `bad_signature`     | `X-Vapi-Signature` header is missing or does not match `MASKER_VAPI_WEBHOOK_SECRET` |
| `404`  | `agent_not_found`   | No agent with this ID exists                                                        |
| `422`  | `unsupported_event` | The `type` field is not `assistant-request`                                         |
