# Per-call post-call actions (/docs/per-call-post-call-actions)

> Choose where one specific call's results are sent, and which variables are extracted, at dial time, without changing the agent's own configuration.

An agent has post-call actions you configure once: the email that receives
the call report, the webhook that receives the call data, and the variables
extracted from the conversation. Sometimes one agent dials on behalf of many
downstream systems, and each call's result belongs somewhere different: a
different webhook per customer, a different set of variables per campaign, or
a report emailed to the person who asked for the call.

Send `__post_call_actions` when you start the call and that call's results go
where you say, without touching the agent's own settings and without affecting
any other call in flight.

It works on a single dispatched call and on a web call session.

## What it changes [#what-it-changes]

The value is **exactly** the `post_call_actions` object you send when you
[create an agent](/docs/api-reference/agents/createAgent): an `email` action,
a `webhook` action, or both. Email and webhook are the only actions a call can
carry.

The actions you send **replace** the agent's configured post-call actions for
that one call. They are not added to them. For the call you dialed:

* only the deliveries you sent fire. The agent's own post-call actions (email,
  webhook, Slack, HubSpot, Salesforce, Sheets, WhatsApp, Workflow and the rest
  configured on the agent) do not run for this call.
* only the variables you sent are extracted from the conversation. The agent's
  own extracted variables are not.

Everything else about the agent (its prompt, voice, languages, tools) is
unchanged, and so is the saved agent.

## Dialing one call [#dialing-one-call]

Add `__post_call_actions` to the dispatch request.

```json
{
  "agent_id": 158910,
  "to_number": "+15551234567",
  "__post_call_actions": {
    "webhook": {
      "enabled": true,
      "url": "https://hooks.acmecrm.io/omnidim/calls",
      "include": ["summary", "extracted_variables"],
      "extracted_variables": [
        { "key": "appointment_date", "prompt": "The date the customer agreed to, in YYYY-MM-DD." },
        { "key": "callback_requested", "prompt": "true if the customer asked to be called back, else false." }
      ]
    },
    "email": {
      "enabled": true,
      "recipients": ["demo@example.com"],
      "include": ["summary", "sentiment", "fullConversation"]
    }
  }
}
```

The response tells you what will run:

```json
{
  "success": true,
  "requestId": 91422,
  "status": "dispatched",
  "custom_variables_count": 0,
  "languages_applied": [],
  "languages_ignored": [],
  "post_call_actions_applied": ["email", "webhook"],
  "post_call_actions_ignored": []
}
```

You can also put `__post_call_actions` inside `call_context` if that suits
your integration better. It behaves the same way, and it is never passed to the
agent as a context variable.

The webhook receives the normal post-call payload, the same one an agent's own
webhook receives. The variables you sent appear under
`call_report.extracted_variables`:

```json
{
  "call_status": "completed",
  "call_report": {
    "summary": "The customer booked an appointment for October 3.",
    "extracted_variables": {
      "appointment_date": "2026-10-03",
      "callback_requested": false
    }
  }
}
```

## When something is rejected [#when-something-is-rejected]

  A call is never rejected because of its post-call actions. Anything that
  cannot be used is skipped and listed in `post_call_actions_ignored`, and the
  call still goes out. If nothing you sent can be used, the agent's own
  post-call actions run instead.

A webhook URL must be a publicly reachable endpoint. Local, private network and
cloud-internal addresses cannot receive your call data, so they are rejected.
This is the same check the agent's own webhook setting uses.

```json
{
  "agent_id": 158910,
  "to_number": "+15551234567",
  "__post_call_actions": {
    "webhook": {
      "enabled": true,
      "url": "http://localhost:8000/hook"
    },
    "email": {
      "enabled": true,
      "recipients": ["demo@example.com"],
      "include": ["summary"]
    }
  }
}
```

The webhook is dropped, the email still runs:

```json
{
  "success": true,
  "requestId": 91423,
  "status": "dispatched",
  "custom_variables_count": 0,
  "languages_applied": [],
  "languages_ignored": [],
  "post_call_actions_applied": ["email"],
  "post_call_actions_ignored": [
    {
      "channel": "webhook",
      "code": "invalid_url",
      "message": "Webhook URL must point to an address reachable from the internet. Local and private network addresses cannot receive your call data. If you are testing locally, use a tunnel URL instead."
    }
  ]
}
```

Each ignored item carries a `code`:

| Code                  | Meaning                                                                                                                                                                                  |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_url`         | The webhook URL is missing, is not `http://` or `https://`, looks like a placeholder, or is not reachable from the internet.                                                             |
| `disabled`            | The action was sent without `"enabled": true`.                                                                                                                                           |
| `no_recipients`       | The email action has no recipient addresses.                                                                                                                                             |
| `unsupported_channel` | A channel other than `email` or `webhook`, such as `slack`. Those need an integration set up on the agent.                                                                               |
| `duplicate_variable`  | The same variable key was sent on both actions with different prompts. The first one wins and the later one is dropped. The same key with the same prompt is fine.                       |
| `unsupported_route`   | The key was sent on a route that does not honor it. See [Campaigns](#campaigns).                                                                                                         |
| `invalid_directive`   | `__post_call_actions` is not an object with `email` and/or `webhook`: a string, list, number, `null` or an empty object. `channel` is `null`, and the agent's own post-call actions run. |

The two response fields appear only when you sent `__post_call_actions`. A
request without it gets the usual response.

## Starting a web call [#starting-a-web-call]

Send it with the session's custom variables when you create the session.

```json
{
  "agent_id": 158910,
  "type": "voice",
  "custom_variables": {
    "name": "Rahul",
    "__post_call_actions": {
      "webhook": {
        "enabled": true,
        "url": "https://hooks.acmecrm.io/omnidim/web-calls",
        "include": ["summary", "extracted_variables"],
        "extracted_variables": [
          { "key": "product_interest", "prompt": "The product the visitor asked about." }
        ]
      }
    }
  }
}
```

The key is removed before your other variables reach the agent, so `name` is
passed through and `__post_call_actions` is not. The response carries the same
two fields alongside the session:

```json
{
  "session_id": 4521,
  "token": "sess_51gF2qw8LxNz0vY4mT7Ka3RjD9pBcE6HuWiQnZsX0oM",
  "expires_at": "2026-10-01T12:15:00Z",
  "ws_url": "wss://live.omnidim.io/chat/start_voice_chat?request_token=sess_51gF2qw8LxNz0vY4mT7Ka3RjD9pBcE6HuWiQnZsX0oM",
  "post_call_actions_applied": ["webhook"],
  "post_call_actions_ignored": []
}
```

## Campaigns [#campaigns]

Bulk campaigns do not support per-call post-call actions. A campaign's calls
use the agent's own post-call actions.

If a contact sent to the bulk create or add-contact endpoints carries
`__post_call_actions`, the key is removed from that contact, the contacts are
still added, and the response tells you it was not used:

```json
{
  "post_call_actions_ignored": [
    {
      "channel": null,
      "code": "unsupported_route",
      "message": "__post_call_actions is honoured only on /api/v1/calls/dispatch and /api/v1/sessions/create; it was ignored for these contacts."
    }
  ]
}
```

A `__post_call_actions` column in a CSV upload is not supported either.

## Parameter [#parameter]

`trigger_call_statuses` decides which call outcomes fire an action. Leave it
out to use the default, `completed` and `voicemail_detected`. See
[Create agent](/docs/api-reference/agents/createAgent) for every field.

## Frequently asked questions [#frequently-asked-questions]

  
    No. The actions apply to the one call you dialed. The agent's saved post-call
    configuration is untouched, and other calls running at the same time are
    unaffected.
  

  
    No. What you send replaces the agent's actions for that call. To keep the
    agent's email and add a webhook, send both in `__post_call_actions`.
  

  
    No. Those deliveries need an integration or a template set up on the agent, so
    they cannot be sent with a call. They are reported as `unsupported_channel`.
  

  
    The call still goes out, and the agent's own post-call actions run as if you
    had not sent the key. `post_call_actions_applied` is empty and
    `post_call_actions_ignored` says why.
  

  
    If `__post_call_actions` is a string, list, number, `null` or an empty object,
    the call still goes out with the agent's own post-call actions.
    `post_call_actions_ignored` holds one item with code `invalid_directive` and
    `channel` set to `null`.
  

  
    No. It is a dialing instruction, not a variable. It is removed before your
    other fields are passed to the agent, on every route.
  

  
    No. Per-call post-call actions are an API feature. To change what every call
    does, edit the agent's post-call actions instead.
  

  
    No. The actions are chosen when you start the call, and an inbound caller
    arrives without that step. The workflow call node does not pass it through
    either. Both use the agent's own post-call actions.
  

  
    Yes. Send `__languages` and `__post_call_actions` on the same request; each
    works independently.
  

## Related [#related]

  " title="Post-call actions" href="/docs/dashboard-guides/post-call" description="Configure an agent's own post-call deliveries, and the webhook payload reference." />

  " title="Per-call languages" href="/docs/per-call-languages" description="Set the languages one call runs in, chosen at dial time." />

  " title="Dispatch call API" href="/docs/api-reference/calls/dispatchCall" description="Full request and response reference for dialing a single call." />