# Agent actions Source: https://docs.nedzo.ai/agents/actions Configure actions your AI agent can perform during conversations, like booking meetings, transferring calls, sending messages, and calling external APIs. Actions let your agent do things during a conversation — book a meeting, transfer a call, send an email, post to Slack, or call an external API. Configure actions from the **Actions** tab. Each action has a **condition** that tells the agent when to use it. Write it in plain language, like *"When the contact wants to book an appointment"* or *"When the caller asks to speak with a manager"*. ## Calendar booking Book meetings on your connected calendar during a call or chat. **Setup:** 1. Connect a calendar provider in **Settings > Integrations** (Google Calendar, Calendly, Cal.com, or GoHighLevel) 2. Add a calendar action on the Actions tab 3. Set the condition, select the provider, and choose the calendar **Configuration:** | Field | Description | | ------------- | ----------------------------------------------------------- | | Name | Action display name | | Condition | When the agent should offer booking | | Calendar type | Google Calendar, Calendly, Cal.com, or LeadConnector | | Calendar ID | Which specific calendar to use | | Timezone | IANA timezone, or leave blank to let the AI ask the contact | The agent checks real-time availability and books directly on your calendar. The default booking flow (asking for the contact's timezone, offering a couple of slots at a time, collecting name + email only after a slot is selected) lives in your agent's prompt and is fully editable. See [Calendar booking instructions](/agents/calendar-booking-instructions) to customize it. ## Call transfer Transfer a live call to a human or another phone number. **Transfer types:** | Type | Description | | ---- | ----------------------------------------------------------- | | Warm | The agent introduces the caller before connecting them | | Cold | The agent connects the caller directly without introduction | **Configuration:** | Field | Description | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Name | Transfer target name (e.g., "Sales Team") | | Condition | When to transfer (e.g., "Caller asks for a human") | | Transfer type | Warm or Cold | | Phone number | E.164 format destination number | | Transfer sentence | The exact sentence the agent speaks immediately before connecting the call. Use this to set the right language, tone, and any handoff context (e.g., *"Un momento, le paso con un agente."*). | The agent speaks the **Transfer sentence exactly as configured**, in whatever language you write it. There is no hard-coded English fallback — if you leave the field blank, the agent stays silent through the handoff. Set the sentence in the language your callers speak. ## Email Send an email during or after a conversation. **Requires:** A verified email domain in **Settings > Integrations > Email**. **Configuration:** | Field | Description | | -------------- | -------------------------------------------------------------------------------------- | | Name | Action name | | Condition | When to send | | From name | Sender display name | | From address | Email local part (domain comes from your email integration) | | Subject | Email subject (supports `{{contact.X}}` variables) | | Body | The email content. Supports `{{contact.X}}` variables and the dynamic variable picker. | | Recipient type | Contact's email, a specific address, or ask the contact | ### Confirming recipient on voice calls When the agent triggers Email during a **live voice call**, it does not send blindly. Before dispatching the email it confirms two things out loud with the caller: 1. **Recipient name** — who the email is for. If the contact record on file has a name, the agent confirms that name. If there is no contact yet, the agent collects the name from the caller. 2. **Destination email address** — the address to send to. The agent reads it back character-by-character (including domain) and asks the caller to confirm before sending. If the caller corrects either value, the agent uses the corrected value and asks for confirmation again. The email is only sent after explicit confirmation. This same flow runs whether the caller is an existing contact or a brand-new one — for new contacts, both the name and the email are collected during the call and saved on the contact record afterwards. ## Slack message Post a message to a Slack channel during a conversation. **Requires:** Slack connected in **Settings > Integrations > Slack**. **Configuration:** | Field | Description | | --------- | --------------------------------------------------------------------------------------- | | Name | Action name | | Condition | When to send | | Channel | Which Slack channel to post to | | Message | Slack message body. Supports `{{contact.X}}` variables and the dynamic variable picker. | ## SMS Send a text message to a contact during a conversation. **Configuration:** | Field | Description | | --------- | -------------------------------------------------------------------------------- | | Name | Action name | | Condition | When to send | | Message | SMS content. Supports `{{contact.X}}` variables and the dynamic variable picker. | ### Confirming recipient on voice calls When the agent triggers SMS during a **live voice call**, it confirms the recipient before sending: 1. **Recipient name** — who the SMS is for. If the contact has a name on file, the agent confirms it; if there is no contact yet, the agent asks for the name. 2. **Destination phone number** — the agent reads back the phone number digit-by-digit (including country code) and asks the caller to confirm before sending. If the caller corrects either value, the agent uses the new value and re-confirms. The SMS is only sent after explicit confirmation. For brand-new contacts, the name and phone collected during this flow are saved on the contact record afterwards. ## Custom action Call any external API during a conversation. Use this to look up data, update records, or trigger actions in systems that don't have a native integration. **Configuration:** | Field | Description | | ---------- | --------------------------------------------------------- | | Name | Action name | | Condition | When to trigger | | Method | GET, POST, PUT, PATCH, or DELETE | | URL | The endpoint to call | | Parameters | Named parameters the agent collects from the conversation | | Auth type | None, Bearer token, API key, or Basic auth | | Headers | Custom HTTP headers | **How parameters work:** Define parameters with a name and description. The agent gathers the required information from the conversation and includes it in the API call. For example, a parameter named `order_number` with description *"The customer's order number"* tells the agent to ask for and extract that value. ### Request format When the action triggers, the voice engine sends an HTTP request to your configured URL. The request body contains a `message` object with the tool call details and the parameters the agent extracted from the conversation. **Example request:** ```json theme={null} POST https://your-api.com/check-order Content-Type: application/json Authorization: Bearer your-secret-token { "message": { "toolCallList": [ { "id": "test_call_id", "type": "function", "function": { "name": "check_order", "arguments": { "order_number": "ORD-12345", "customer_name": "John Doe" } } } ] } } ``` The `toolCallList` array contains one object per tool call. Each tool call includes: | Field | Type | Description | | -------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------ | | `id` | string | Unique identifier for this tool call | | `type` | string | Always `"function"` | | `function.name` | string | The action name (lowercased, spaces replaced with underscores) | | `function.arguments` | object | Key-value pairs where each key matches a parameter name you defined, and the value is what the agent extracted from the conversation | **Headers sent with the request:** | Auth type | Header added | | ------------ | ----------------------------------------- | | Bearer token | `Authorization: Bearer {your token}` | | API key | `X-API-Key: {your key}` | | Basic auth | `Authorization: Basic {your credentials}` | Any custom headers you configured are also included. ### Response format Your endpoint must return a JSON response. The agent reads the response and uses it to continue the conversation. **Successful response (200):** ```json theme={null} { "results": [ { "toolCallId": "test_call_id", "result": "Order ORD-12345 is currently in transit and expected to arrive on January 20th." } ] } ``` The `results` array should contain one object with: | Field | Type | Description | | ------------ | ------ | -------------------------------------------------------- | | `toolCallId` | string | The tool call ID from the request (echo it back) | | `result` | string | The information the agent should use in the conversation | The agent takes the `result` string and incorporates it into its response to the contact. Keep the result concise and factual — the agent will phrase it naturally. **Error response:** If something goes wrong, return an error message in the result. The agent will handle it gracefully: ```json theme={null} { "results": [ { "toolCallId": "test_call_id", "result": "Error: Order not found." } ] } ``` The action executes synchronously — the call pauses briefly while waiting for your endpoint to respond. Keep your endpoint fast (under a few seconds) to avoid awkward silence during the call. ## Managing actions * Click **Add** to create a new action * Use the kebab (`...`) menu on each action to **Edit**, **Duplicate**, or **Delete** it * Toggle **Active/Inactive** on each action to enable or disable it without deleting ### Duplicate Duplicating clones an existing action as a starting point for a new one. Available on every action type, but most useful for **custom actions** — most teams build a small set of similar custom actions (same auth, similar headers, similar URL patterns) and duplicating saves rewriting that boilerplate. The duplicated action: * Is created inactive — toggle it on once you've finished editing. * Gets a new name with " (copy)" appended; rename it before saving. * Copies every field: condition, method, URL, parameters, auth type, headers, and any provider-specific settings. * Is independent of the original — editing one does not affect the other. # Active hours Source: https://docs.nedzo.ai/agents/active-hours Set a weekly schedule for when your inbound voice agent answers calls. Configure active hours, time zones, and behavior outside business hours. Set a weekly schedule for when your inbound voice agent is available to take calls. Outside of active hours, calls won't be answered by the agent. This feature is only available for **inbound voice agents**. Configure it from the **Settings** tab under **Advanced Settings**. ## Setting up a schedule 1. Toggle **Schedule enabled** on 2. For each day of the week, set the hours your agent should be available 3. Toggle individual days on or off ### Time slots Each day can have one or more time slots: * Set a **From** and **To** time for each slot * Times are in 30-minute increments (e.g., 9:00 AM, 9:30 AM, 10:00 AM) * Click **Add time slot** to add multiple windows per day (e.g., morning and afternoon) * Remove a slot by clicking the **X** next to it ### Overnight schedules If your **To** time is earlier than your **From** time, Nedzo treats it as an overnight window. For example, 10:00 PM to 6:00 AM means the agent is active through the night. An **+overnight** label appears to confirm this. ## Copying schedules Instead of configuring each day manually, copy one day's schedule to others: 1. Set up the hours for one day 2. Click the **Copy** button on that day 3. Choose where to apply it: * Individual days (checkboxes) * All weekdays * Weekends * All days ## Outside active hours When a call comes in outside the scheduled hours, the agent won't pick up. You can pair this with your phone provider's settings to route calls to voicemail or another number during off-hours. # Calendar booking instructions Source: https://docs.nedzo.ai/agents/calendar-booking-instructions The booking flow your agent uses out of the box, and how to customize it. When you connect a calendar action to an agent, we add a default block of booking instructions to your agent's prompt. The agent uses these to ask for a timezone, present a couple of slots at a time, and collect the contact's name and email only after they've picked a time. You can edit these instructions directly in the prompt editor. They are part of your agent's prompt — they are not hidden from you and they are not enforced by the platform. Tune them, replace them, or remove them entirely. ## The default block Copy this into your agent's prompt if you ever need to restore the defaults. ```text theme={null} OPERATIONAL RULES: 1) Conversation Memory (CRITICAL): If the user's timezone, name, or email has already been provided earlier in this conversation, remember and reuse that information. NEVER re-ask for information already collected. 2) Timezones (REQUIRED FIRST): Before checking availability, ask the user for their timezone only if it has not been provided yet in this conversation. Always convert tool outputs to the user's local time when speaking. 3) Identity Verification (ONLY WHEN BOOKING): Only ask for Full Name and Email when the user has selected a slot and is ready to book. If name and email were already collected earlier in the conversation, skip this step and proceed directly to booking. Do NOT ask for name/email before showing availability. 4) Presenting Availability: Offer a maximum of two slots at a time. Use relative dates (e.g., "Tomorrow," "This Thursday"). Speak naturally, no bullet lists. BOOKING WORKFLOW: 1) Get Timezone only if not already known from this conversation 2) Check Availability with fetch_slots 3) Offer Slots (max 2 at a time) 4) Collect Contact Info only AFTER slot selection — if already known, confirm: "I'll book this under [name], [email] — shall I go ahead?" 5) Finalize with book ``` ## Why each rule exists * **Conversation Memory** — Without this, agents re-ask for timezone or email after every tool call, which feels broken to the contact. * **Timezones first** — Booking against the wrong timezone produces calendar invites the contact never sees. Asking up front is cheaper than apologizing later. * **Identity only at booking** — Asking for name/email before showing availability comes across as gating. Asking after a slot is selected feels like a normal confirmation step. * **Two slots at a time** — Voice channels can't read a list of ten options. Even on chat, two options at a time keeps the back-and-forth fast. * **Booking workflow ordering** — Codifies the sequence so the agent doesn't try to book before checking availability. ## Customizing You can edit any of these freely. Common tweaks: * **Offer more or fewer slots** — change "two slots" to "three slots" if you want a wider menu, or "one slot" for a more directive feel. * **Change the language** — these instructions affect tone. If your brand is formal, rewrite them as full sentences in that voice; the model will mirror it. * **Add booking-window rules** — e.g. "never offer slots within the next 4 hours" or "only weekday afternoons". * **Remove identity verification** — if you already authenticated the contact (e.g. logged-in web chat), skip the name/email confirmation by deleting rule 3. ## Calendar timezone Each calendar action has an optional **Timezone** field (IANA, e.g. `America/New_York`). When set, the agent will not ask the contact for a timezone — it will book against the calendar's timezone instead. Leave it blank if your contacts span multiple timezones and the agent should always ask. ## Tool wiring (handled automatically) You don't need to add the tool names (`fetch_slots`, `book`, `cancel`, `reschedule`) or the calendar provider/ID to your prompt. Those are still injected automatically when you attach a calendar action — only the operational rules above live in your editable prompt. # Call analysis Source: https://docs.nedzo.ai/agents/call-analysis Set up automatic post-call analysis for Nedzo voice agents. Generate summaries and extract structured data from calls. After every voice call, Nedzo can automatically analyze the conversation. Configure these features from the **Settings** tab under **Conversation Analysis**. ## Summary generation Generate a concise summary of every call. When enabled, each call gets an automatic summary that includes: * Key discussion points * Outcomes and decisions * Action items * Any notable details Summaries appear on the call detail page in your dashboard. You can customize the summary prompt to focus on what matters to your business. **Custom prompt example:** *"Summarize the call focusing on: the contact's main pain point, any pricing discussed, and the agreed next steps."* ## Call disposition Automatically classify the outcome of each call into a category. **Disposition categories:** | Category | Description | | ------------------ | -------------------------------------- | | Interested | Contact expressed interest | | Not Interested | Contact declined or showed no interest | | Appointment Booked | A meeting was scheduled | | Follow-Up Required | Needs another touchpoint | | Wrong Number | Reached the wrong person | | Voicemail | Left a voicemail | | Do Not Call | Contact requested no further calls | | Other | Doesn't fit other categories | You can customize the disposition prompt to adjust how calls are classified. ## Data extraction Extract structured data from conversations automatically. This is useful for pulling out specific information that your team needs. ### Adding extraction fields 1. Click **Add Field** under Data Extraction 2. Configure the field: | Setting | Description | | ----------- | -------------------------------------------------------------- | | Name | Field identifier (e.g., "budget", "timeline") | | Type | Text, Number, or Yes/No | | Description | What the field captures (e.g., "The contact's monthly budget") | 3. The agent extracts this data from every call ### Field types | Type | Output | Example | | ------ | ---------------- | --------- | | Text | Free-form string | "Q3 2025" | | Number | Numeric value | 5000 | | Yes/No | Boolean | Yes | ### Use cases * **Budget** — Extract the contact's stated budget (Number) * **Decision timeline** — When they plan to make a decision (Text) * **Has authority** — Whether they're the decision maker (Yes/No) * **Pain points** — Main challenges mentioned (Text) * **Competitor** — Any competitor names mentioned (Text) Extracted data appears on the call detail page and can be used for filtering and reporting. # Compliance Source: https://docs.nedzo.ai/agents/compliance Turn on recording consent and AI disclosure per channel. Both are off by default and are spoken or sent at the start of a conversation in your agent's language. Nedzo can tell people they are dealing with an AI, and that a call is being recorded. Both are **off by default**. You turn each one on yourself, per channel, and nothing is ever forced on you based on where a contact is calling from. Find these under **Ned → Deploy**, in the **Compliance** section of each channel. ## What you can turn on | Setting | Channels | What it does | | --------------------- | ------------------ | ------------------------------------------------------------------------- | | **AI disclosure** | Phone, Chat, Email | Tells the person they are dealing with an AI assistant from your business | | **Recording consent** | Phone only | Tells the caller the call is being recorded | Recording consent is phone-only on purpose. There is nothing to consent to recording on chat or email. ## Turning them on 1. Open **Ned → Deploy** 2. Pick the channel: **Phone**, **Chat**, or **Email** 3. Find the **Compliance** section 4. Toggle **AI disclosure** on, and on Phone also **Recording consent** if you want it Each row shows **On** or **Off** so you can see the current state without opening it. ## What the contact hears or reads The wording is fixed, so you do not have to write it. Your business name is filled in automatically. ### Phone The message plays at the very start of the call, before your agent's opening line. * **AI disclosure only:** "Please note that you're speaking with an AI assistant from your business." * **Recording consent only:** "Please note that this call is being recorded for quality and training purposes." * **Both on:** the two are combined into one sentence, so the caller hears a single preamble instead of two. ### Chat Sent as the first message of a new AI-handled conversation: "Please note that you're chatting with an AI assistant from your business." ### Email Included in the first reply: "Please note that this message was sent by an AI assistant from your business." ## Languages The text matches your agent's language. Eight are covered: English, Spanish, French, German, Portuguese, Dutch, Chinese, and Japanese. You do not need to translate anything. ## Testing it The **Test** preview shows the disclosure too, on phone, chat, and email. So you can turn a toggle on and hear or read exactly what a real contact would get before you go live. ## Things worth knowing * **Off by default.** An agent you never touch behaves exactly as it does today. * **Per channel, per agent.** Turning AI disclosure on for Chat does not turn it on for Email. * **Never forced.** Nedzo does not switch these on for you based on the caller's location. Whether you need them is your call. * **One preamble, not two.** With both phone settings on, the caller hears one combined sentence. If you operate in the EU, the AI Act (Article 50) expects people to be told when they are interacting with an AI. That covers phone, chat, and email, which is why AI disclosure is available on all three. Recording consent rules vary by country and by US state. Nedzo gives you the switches; which ones you need is a decision for you and your legal advisor. # Knowledge base Source: https://docs.nedzo.ai/agents/knowledge-base Add files, URLs, and text to your agent's knowledge base. The agent searches this content during conversations for accurate, context-aware answers. The knowledge base gives your agent access to information it can reference during conversations. When a contact asks a question, the agent searches the knowledge base for relevant content and uses it to respond accurately. Configure knowledge sources from the **Knowledge** tab. ## Source types ### Website Add a URL and Nedzo crawls the page content. Good for: * FAQ pages * Product documentation * Pricing pages * Company information If you entered your website during onboarding, it's already here — Nedzo ingests it as your first knowledge source, so you don't need to add it again. **Indexing status** — A newly added website shows an **Indexing** chip while Nedzo crawls it, then switches to indexed once the content is searchable. The chip updates on its own; you don't need to reload the page. Your agent can only answer from a source after it finishes indexing. ### File Upload documents for your agent to reference. Supported formats include PDFs and other common document types. Good for: * Product catalogs * Policy documents * Training materials * Price lists ### Text Paste text directly. Good for: * Quick notes or context * Frequently asked questions * Scripts and talking points * Temporary information ## Managing knowledge Click **Manage** on the Knowledge tab to open the knowledge panel. From here you can: * **Browse** all knowledge items in your workspace * **Search** by name or content * **Filter** by type (Website, File, Text) * **Link** existing knowledge items to your agent * **Unlink** items you no longer need on this agent * **Create** new knowledge items directly Knowledge items are shared across your workspace. One item can be linked to multiple agents, so you don't have to duplicate content. ## Correcting an answer When an agent answers a question badly, you can correct it on the spot and save the correction as knowledge. The next time someone asks, the agent uses your version. **From Unibox:** hover a message the AI sent and click **Improve message**. Write the answer you wanted and save it. **While testing an agent:** correct the answer directly in the test panel. Either way the correction is saved as a question-and-answer pair in your workspace knowledge, under **Improved questions**. Corrections carry higher retrieval priority than ordinary content, so a corrected answer wins over a page that says something older or vaguer. Corrections are shared across your workspace like any other knowledge item, so fixing an answer once fixes it everywhere that item is linked. ## How it works When a contact asks a question, the agent: 1. **Rewrites the query** against the last few turns of the conversation — so "how much is it?" becomes a query that includes what "it" actually refers to. This dramatically improves recall on follow-up questions. 2. **Pre-fetches** relevant passages from the linked knowledge base using semantic search **before** the LLM is called. The retrieved content is injected directly into the agent's context. 3. **Generates an accurate response** using the pre-fetched passages, plus any conversation history and the agent's prompt. 4. **Cites the information** naturally in the conversation — including numbered citations on chat / web replies (see [Source attribution](#source-attribution)). The agent only references knowledge you've linked to it — not knowledge from other agents in your workspace. ### Prefetch vs. on-demand lookup There are two retrieval modes: * **Prefetch (default)** — Knowledge is fetched before the model runs, on every contact turn that could benefit from it. This is the new default and is what you want in almost all cases — the model has the information up front, replies are faster, and citations are reliable. * **On-demand lookup tool** — In older agents, the model decides whether to call a `lookup_information` tool, then gets the result and replies. This is now **automatically suppressed** when prefetch returns useful results, to avoid a redundant second round-trip. If your agent has the lookup tool enabled and you want to keep its old behavior (always let the model decide), there is an opt-in override in the agent settings; most agents should leave this off. Prefetch covers all conversational channels: voice calls, SMS / WhatsApp / Email / Instagram / Messenger via chat agents, and Web Agents. ## Source attribution When the AI uses content from your knowledge base, responses include numbered superscript citations (¹, ², ³) inline with the text. A footer at the bottom of the message lists all cited sources. For **website** sources, each citation links directly to the original URL. Clicking a citation or footer entry opens the source page in a new tab. File uploads and text sources do not include clickable links since there is no URL to reference — they still show the citation number and source name in the footer. ## Tips * **Keep content focused** — Smaller, specific documents perform better than large general ones * **Update regularly** — Remove outdated information and add new content as your business changes * **Use descriptive names** — Name your knowledge items clearly so you can find them easily * **Test your agent** — After adding knowledge, test the agent to make sure it uses the information correctly # Ned Identity Source: https://docs.nedzo.ai/agents/ned-identity Change the name your AI agent goes by in conversations across chat, voice, email and web. # Ned Identity Your AI agent introduces itself by name and signs emails with it. By default that name is **Ned**. You can change it to anything that fits your brand. ## Where to find it Go to **Settings → Ned AI Agent → Customizations → Ned identity**. The setting sits between **Subscription** and **Channels** in the settings menu. ## What it changes The name applies everywhere the agent speaks to a customer: | Channel | Where the name appears | | ------- | --------------------------------------------- | | `Email` | The sign-off at the end of every reply | | `Chat` | How the agent introduces itself | | `Voice` | How the agent says its own name on a call | | `Web` | How the agent introduces itself in the widget | One name covers every channel. You can't set a different name per channel. ## What it does not change Labels in your own dashboard stay the same. If you rename the agent to "Ava", your team still sees the same menus and pages they saw before. Only the customer-facing name changes. ## Setting a name 1. Open **Settings → Ned AI Agent**. 2. Under **Customizations**, find **Ned identity**. 3. Type the name you want. 4. The change saves on its own. The name applies to new messages right away. Messages already sent keep the name they were sent with. ## Clearing the field Leave the field empty and the agent goes back to **Ned**. There's no separate reset button — an empty field is the reset. ## Examples | You type | Customer sees | | --------------- | ----------------------- | | `Ned` | "Thanks, Ned" | | `Ava` | "Thanks, Ava" | | `Sam from Acme` | "Thanks, Sam from Acme" | | (empty) | "Thanks, Ned" | ## Notes * The name is set once for the whole workspace, not per agent role. * Long names work but read badly in an email sign-off. Keep it short. * The setting takes plain text. Emoji and formatting aren't supported. # Agents overview Source: https://docs.nedzo.ai/agents/overview Build and configure AI agents in Nedzo for voice calls, chat, and web agents. Set up prompts, knowledge bases, actions, call analysis, and active hours. Agents are the core of Nedzo. They handle conversations with your contacts across voice calls, text messages, and web chat. Each agent has its own personality, instructions, voice, and capabilities. Voice, chat, and web agents System prompt, variables, voice, language, and AI model Calendar booking, transfers, email, Slack, SMS, and webhooks Give your agent context from files, websites, and text Summaries, dispositions, and data extraction Schedule when inbound agents accept calls # Post-conversation webhook Source: https://docs.nedzo.ai/agents/post-conversation-webhook Send conversation data to your server after a voice, chat, or web agent finishes. Includes the transcript, summary, and extracted fields. Every agent can fire an outbound webhook the moment a conversation ends. Use it to push transcripts, summaries, dispositions, and extracted fields into your own CRM, data warehouse, or notification system. Previously called the "Post-Call Webhook" (voice only). It now fires for all three agent types: | Agent type | When it fires | | ---------- | -------------------------------------------------------------------------------- | | Voice | After a phone call ends (inbound or outbound) | | Chat | After an SMS, Instagram, Messenger, email, or web chat conversation is finalized | | Web Agent | After a web agent conversation (voice or chat) ends | ## Setup ### In the dashboard 1. Open your agent and go to the **Settings** tab. 2. Under **Post-Conversation Webhook**, paste your endpoint URL and save. 3. Click **Test webhook** to fire a sample payload so you can verify your server accepts it. ### Via API Set `postConversationWebhookUrl` when creating or updating an agent: ```bash theme={null} curl -X PATCH "https://api.nedzo.ai/v1/agents/{agentId}" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed"}' ``` Set the field to `null` to disable the webhook. The legacy field name `postCallWebhookUrl` is still accepted on create/update for backwards compatibility, but API responses always return `postConversationWebhookUrl`. ## Delivery semantics * **Fire-and-forget.** Nedzo sends the request but does not retry on failure. * **Timeout.** The request is aborted if your server takes longer than 10 seconds to respond. * **Success.** Any `2xx` status is logged as delivered. Non-2xx responses and timeouts are logged but do not retry. * **Order.** One webhook per conversation, sent after the conversation has been persisted, summary/extraction has run, and the workflow trigger has been queued. * **URL validation.** Only `https://` (or `http://` for public hosts in development) is allowed. Internal IPs and metadata endpoints are rejected to prevent SSRF. * **HIPAA.** If the agent has HIPAA compliance enabled, `transcript` is omitted from the payload and `summary` is sent as `null`. ## Payload Nedzo sends a `POST` request with `Content-Type: application/json` and a JSON body. ### Common fields Every payload includes these fields, regardless of channel: | Field | Type | Description | | ------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `event` | string | `"conversation.completed"` for chat and web. Voice sends `"call.completed"` for backwards compatibility. | | `channel` | string | One of `voice`, `web`, `sms`, `email`, `whatsapp`, `instagram`, `facebook`, `web_chat`. | | `conversationId` | string (UUID) | The Nedzo conversation record ID. | | `workspaceId` | string (UUID) | The workspace the agent belongs to. | | `startedAt` | string (ISO 8601) | When the conversation started. | | `endedAt` | string (ISO 8601) | When the conversation ended. | | `agent` | object | `{ agentId, name, phone? }`. Use `agent.agentId` to identify the agent. `phone` only included for voice. | | `contact` | object \| null | Contact record, or `null` when no contact was matched/created. Always a fully populated object when present — never an empty `{}`. Voice/web include `{ contactId, firstName, lastName, phone, email, contactBusinessName, tags, customFields }`; chat includes `{ contactId }`. Values in `customFields` are typed JSON primitives (see [Field-value typing](#field-value-typing)). | | `analysis.summary` | string \| null | AI-generated summary. `null` if HIPAA is enabled or summary generation failed. | | `analysis.disposition` | string \| null | AI-classified disposition label when call disposition classification is enabled on the agent. | | `analysis.dataExtraction` | object | Map of configured extraction field names to typed JSON values (`string` / `number` / `boolean` / `null`). See [Field-value typing](#field-value-typing). | | `transcript` | string | Full transcript, one conversation turn per line, separated by newlines (`\n`). For **text channels** (SMS, WhatsApp, Instagram, Facebook, web chat, email) each line is prefixed with a `[YYYY-MM-DD HH:mm:ss]` timestamp in your workspace timezone, e.g. `[2026-06-15 14:23:07] User: ...`. **Voice and web** transcripts are not timestamped and use the plain `Speaker: text` form. In both cases each line is `Speaker: text` (`User:` or `Assistant:`). The field name and string type are unchanged. **Omitted entirely when HIPAA compliance is enabled.** | ### Voice & Web Agent fields | Field | Type | Description | | ----------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `direction` | string | `"inbound"` or `"outbound"`. | | `durationSeconds` | number | Call/session duration in seconds. | | `endedReason` | string \| null | Raw reason the call ended (e.g. `customer-ended-call`, `no-answer`, `voicemail`, `assistant-forwarded-call`). Use this for fine-grained branching. | | `agent.phone` | string \| null | The phone number the agent used (voice only). | | `appointmentDate` | string (ISO 8601) \| null | Confirmed appointment date/time if a calendar booking tool fired during the conversation. Resolved to UTC using the booking tool's configured timezone. `null` when no booking happened. | | `actions` | array | Canonical action log of every tool call the agent executed during the conversation. See [Actions log](#actions-log) below. Empty array when HIPAA is enabled. | ### Chat-only fields | Field | Type | Description | | --------------- | --------------------- | ---------------------------------------------------------------------------------------------------------- | | `messageCount` | number | Number of messages exchanged. | | `lastMessageAt` | string (ISO 8601) | Timestamp of the final message. | | `contactId` | string (UUID) \| null | Contact record, if one was matched/created. Chat payloads also include this convenience field at the root. | ### Web Agent fields Web Agent payloads use the voice-style structure — `endedReason`, `durationSeconds` on the root — plus `channel: "web"`. ## Example payloads ### Voice call ```json theme={null} { "event": "call.completed", "channel": "voice", "conversationId": "0a9b8c7d-...", "workspaceId": "w1w2w3-...", "direction": "outbound", "endedReason": "customer-ended-call", "durationSeconds": 245, "startedAt": "2026-04-23T14:30:00Z", "endedAt": "2026-04-23T14:34:05Z", "contact": { "contactId": "a1b2c3d4-...", "firstName": "John", "lastName": "Doe", "phone": "+14155551234", "email": "john@example.com", "contactBusinessName": "Acme Co", "tags": ["lead", "interested"], "customFields": { "budget": 5000, "is_returning": true, "tier": "gold" } }, "agent": { "agentId": "x1y2z3-...", "name": "Sales Agent", "phone": "+14155550001" }, "analysis": { "summary": "Customer confirmed interest and booked a demo for Friday at 2pm.", "disposition": "Appointment Booked", "dataExtraction": { "budget": 5000, "timeline": "Q3 2026", "demo_requested": true } }, "appointmentDate": "2026-04-26T18:00:00.000Z", "actions": [ { "type": "appointment_booked", "at": "2026-04-23T14:33:47Z", "metadata": { "scheduledFor": "2026-04-26T18:00:00.000Z", "calendarName": "Sales calendar" } } ], "transcript": "User: Hi, I'm calling about the demo.\nAssistant: Hello John, happy to help. Are you free Friday?\nUser: Friday at 2pm works.\nAssistant: Great, you're booked for Friday at 2pm." } ``` Each turn sits on its own line (`Speaker: text`), separated by `\n`. Split on newlines to reconstruct the turn-by-turn conversation. ### Actions log `actions` is a chronological array of every tool call the agent ran during the conversation. Each entry has: | Field | Type | Description | | ---------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `type` | string | One of `appointment_booked`, `call_transfer`, `call_ended_by_assistant`, `sms_sent`, `email_sent`, `custom_action`, `mcp_action`. | | `at` | string (ISO 8601) | When the action fired. | | `metadata` | object | Type-specific payload — booking details, transfer destination, message body, etc. Internal IDs are stripped. | Failed tool calls are not emitted. The full unredacted action log (including tool args) is kept on the conversation record itself; `actions` here is sanitized for outbound delivery. When **HIPAA compliance** is enabled on the agent, `actions` is sent as an empty array since tool arguments can carry protected health information. ### Chat conversation (SMS / Instagram / email / web chat) ```json theme={null} { "event": "conversation.completed", "channel": "sms", "conversationId": "0a9b8c7d-...", "workspaceId": "w1w2w3-...", "contactId": "a1b2c3d4-...", "messageCount": 8, "startedAt": "2026-04-23T14:30:00Z", "endedAt": "2026-04-23T14:45:00Z", "lastMessageAt": "2026-04-23T14:45:00Z", "contact": { "contactId": "a1b2c3d4-..." }, "agent": { "agentId": "x1y2z3-...", "name": "Support Agent" }, "analysis": { "summary": "Visitor asked about pricing tiers and agreed to a follow-up email.", "disposition": "Interested - Demo Scheduled", "dataExtraction": { "interest": "pricing", "seats_needed": 12 } }, "transcript": "[2026-04-23 14:30:12] User: How much does this cost?\n[2026-04-23 14:31:05] Assistant: Our plans start at $49/mo.\n[2026-04-23 14:43:20] User: Can you email me the details?\n[2026-04-23 14:43:58] Assistant: Sure, sending those over now." } ``` Text-channel transcripts prefix each line with `[YYYY-MM-DD HH:mm:ss] ` in your workspace timezone (no offset suffix). If a workspace hasn't set a timezone, times render in UTC. Strip the leading `[...] ` to recover the plain `Speaker: text` line. ### Web Agent conversation ```json theme={null} { "event": "conversation.completed", "channel": "web", "conversationId": "0a9b8c7d-...", "workspaceId": "w1w2w3-...", "endedReason": "customer-ended-call", "durationSeconds": 132, "startedAt": "2026-04-23T14:30:00Z", "endedAt": "2026-04-23T14:32:12Z", "contact": { "contactId": "a1b2c3d4-...", "firstName": "Jane", "lastName": "Smith", "phone": null, "email": "jane@example.com", "contactBusinessName": null, "tags": [], "customFields": {} }, "agent": { "agentId": "x1y2z3-...", "name": "Website Assistant" }, "analysis": { "summary": "Visitor asked about onboarding and asked for a demo link.", "disposition": "Interested - Demo Scheduled", "dataExtraction": { "intent": "demo_request", "team_size": 25 } }, "appointmentDate": null, "actions": [ { "type": "email_sent", "at": "2026-04-23T14:32:05Z", "metadata": { "to": "jane@example.com", "subject": "Your demo link", "body": "Hi Jane, here's the link..." } } ], "transcript": "User: Hi, I was looking at your pricing page.\nAssistant: Happy to help. What size is your team?\nUser: About 25 people.\nAssistant: I'll send over a demo link tailored to that." } ``` ## Field-value typing `analysis.dataExtraction` and `contact.customFields` ship values as the JSON primitive that matches each field's declared type. The mapping: | Declared type | JSON output | Example raw value | Example output | | ------------------------- | --------------------------- | ------------------------------- | ------------------------- | | `number` | `number` (finite) or `null` | `"42"` / `"3.14"` / `"-7"` | `42` / `3.14` / `-7` | | `number` (unparseable) | `null` | `"abc"` / `""` / `"NaN"` | `null` | | `boolean` | `true` / `false` / `null` | `"true"` / `"TRUE"` / `"False"` | `true` / `true` / `false` | | `boolean` (non-canonical) | `null` | `"yes"` / `"1"` / `""` | `null` | | `text` / `string` | `string` | `"gold"` | `"gold"` | | `date` | `string` (ISO 8601) | `"2026-05-20"` | `"2026-05-20"` | | unset / unknown | `string` (raw) | `"anything"` | `"anything"` | Notes: * A field that is not yet populated for the conversation/contact is omitted from the map rather than emitted as `null`. * `null` in `customFields` / `dataExtraction` always means *the stored value could not be coerced to the declared type* — log it on your side if you care about type drift. * Booleans accept canonical `'true'` / `'false'` only (case-insensitive, trimmed). Legacy non-canonical values like `'yes'` / `'1'` are intentionally surfaced as `null` instead of silently flipped to `false`. * Dates are emitted as the raw stored string (typically ISO 8601). JSON has no native date type. ## Security * **Always use HTTPS.** Nedzo blocks requests to non-public IPs and metadata endpoints but you should also refuse plaintext HTTP on your end. * **Verify the source.** Nedzo does not currently sign outbound webhooks. If you need to verify the sender, host your endpoint behind an auth gateway, allowlist Nedzo IP ranges, or include a shared secret in the URL path. * **Be idempotent.** Use `conversationId` as your idempotency key so duplicate deliveries (e.g. from network retries on your side) don't create duplicate records. ## Related * [Conversation ended workflow trigger](/workflows/triggers/conversation-completed) — to branch Nedzo workflows after any voice, chat, or web conversation, use this trigger instead of the webhook. * [Workflow webhook trigger](/workflows/triggers/webhook) — for sending requests *into* Nedzo from your own systems. # Prompt and identity Source: https://docs.nedzo.ai/agents/prompts-and-models Configure your AI agent's system prompt, personality, voice, language, and model. Define conversation goals, rules, and the identity that makes it unique. The **Prompt** tab is where you define your agent's personality, instructions, voice, and AI model. This is the core of what makes your agent unique. ## System prompt The system prompt is the main set of instructions for your agent. It defines the agent's personality, goals, rules, and conversation flow. Write your prompt in the large text editor on the right side of the Prompt tab. A token counter at the bottom shows the approximate size. **Tips for good prompts:** * Be specific about the agent's role and goals * Define what the agent should and shouldn't do * Include example phrases or responses * Describe how to handle edge cases Use the **Enhance with AI** button to get suggestions for improving your prompt. ## Opening line The opening line is the first thing your agent says when a conversation starts. Set it in the left column of the Prompt tab. **Example:** *"Hi there, is this `{{contact.firstName}}`?"* ## Variables Use variables in your prompt and opening line to personalize conversations. Click the **Variables** button to see all available options. Nedzo uses **dot notation** for variables — `{{contact.firstName}}`, `{{trigger.conversation.summary}}`, etc. Pick the namespace, then drill in. The legacy flat syntax (`{{contactFirstName}}`, `{{contactEmail}}`, etc.) still works for backwards compatibility with existing prompts and workflows. New prompts should use dot notation — it's clearer, scales to nested data like custom fields, and matches the variable picker. ### System variables | Variable | Description | Example | | --------------- | --------------- | ---------- | | `{{date}}` | Current date | 2025-01-15 | | `{{time}}` | Current time | 2:30 pm | | `{{dayOfWeek}}` | Day of the week | Monday | ### Contact variables | Variable | Legacy alias | Description | | ------------------------- | ------------------------- | ----------------------- | | `{{contact.firstName}}` | `{{contactFirstName}}` | Contact's first name | | `{{contact.lastName}}` | `{{contactLastName}}` | Contact's last name | | `{{contact.email}}` | `{{contactEmail}}` | Contact's email address | | `{{contact.phone}}` | `{{contactPhone}}` | Contact's phone number | | `{{contact.companyName}}` | `{{contactBusinessName}}` | Contact's company name | Custom fields are available under `{{contact.customFields.}}`. ### Custom variables You can create workspace-level custom variables for data that's specific to your business. These are available across all agents in the workspace. ## LLM model Choose which AI model powers your agent. Select a model from the dropdown in the left column of the Prompt tab. ### Supported providers | Provider | Models | | --------- | -------------------------------------------- | | OpenAI | GPT-4o and other available models | | Anthropic | Claude 3.5 Sonnet and other available models | | Google | Gemini 1.5 Flash and other available models | If you don't select a model, the agent uses your workspace's default. ### Temperature Temperature controls how creative or deterministic the agent's responses are: * **Lower values** (e.g., 0.3) — More consistent, predictable responses. Good for support, factual tasks, and reliable tool calls. * **Higher values** (e.g., 0.9) — More varied, creative responses. Good for sales and casual conversation. * **Default:** 0.3 ## Language Set the agent's language from the Prompt tab. Supported languages: * English * Spanish * Portuguese * French * German * Dutch * Chinese * Japanese Changing the language resets your voice selection, since voices are language-specific. ## Voice Give your agent a natural-sounding voice. Voice settings are available for voice agents and web agents. ### Voice selection Pick a voice from the dropdown. Voices are filtered by your selected language, so you'll only see voices that match. Each voice shows: * **Name** — The voice identifier * **Gender** — Male or Female * **Accent** — Regional accent variant * **Description** — A short summary of how the voice sounds Click the **play button** next to any voice to preview it before selecting. ### Voice speed Adjust how fast your agent speaks with the speed slider: | Speed | Effect | | ----- | ----------------------- | | 0.7x | Slower, more deliberate | | 1.0x | Normal speed (default) | | 1.2x | Slightly faster | Slower speeds work well for complex information. Faster speeds feel more natural for casual conversations. # Agent types Source: https://docs.nedzo.ai/agents/voice-agents Learn about the three Nedzo agent types: voice agents for phone calls, chat agents for SMS and social messaging, and web agents for your website. Nedzo supports three agent types, each designed for a different channel. All agent types share the same prompt, actions, and knowledge base configuration — the differences are in how they communicate. ## Voice agents Voice agents handle phone calls using AI. They can make outbound calls or answer incoming ones. ### Call direction * **Outbound** — The agent makes calls to your contacts. Used for lead qualification, appointment reminders, follow-ups, and outreach campaigns. * **Inbound** — The agent answers incoming calls. Used for customer support, reception, and intake. You can change the direction at any time from the agent's **Prompt** tab. ### Phone number assignment Voice agents need a phone number to make or receive calls. Assign one from the **Settings** tab under **Telephony**. **Purchased numbers** — Phone numbers you own in your workspace. Both inbound and outbound agents can use these. Each inbound agent needs its own number — two inbound agents can't share the same number. Every new account starts with one free US number, assigned automatically at signup, so an agent has something to use straight away. Buying additional numbers requires a paid plan — see [buying phone numbers on a trial](/billing/managing-your-subscription#buying-phone-numbers-on-a-trial). Some countries require a regulatory bundle before you can purchase a number. When buying a number for one of these countries, you'll see a prompt to create a regulatory bundle first, with a button that takes you to the Trust Center with the country pre-filled. **Verified Caller IDs** — Numbers you've verified for outbound use only. Useful when you want calls to show your existing business number. Not available for inbound agents. ### Choosing a voice Pick your agent's voice under **Ned → Deploy**. Press the play button next to the voice picker to hear a short sample before you commit. * Press play to hear the selected voice * Press it again to stop * Switch voices and press play again to hear the new one You no longer need to save and call the agent to find out what it sounds like. ### Call settings | Setting | Range | Default | Description | | ----------------- | -------- | ------- | ---------------------------------------------------------- | | Max call duration | 1–60 min | 30 min | The call ends automatically after this time | | Ring duration | 1–60 sec | 30 sec | How long to ring before marking as "No Answer" | | Background sound | On/Off | On | Plays subtle office ambience to make the call feel natural | ### Voicemail Enable voicemail detection from the **Settings** tab. When turned on: * The agent detects when a call goes to voicemail * It leaves your configured voicemail message * The call is logged with a "Voicemail" disposition You can customize the voicemail message. The default is: *"Hi, could you please call me back?"* ### HIPAA compliance Enterprise plans can enable HIPAA compliance mode. When enabled, call recordings, transcriptions, and logs are not stored. This is configured in the **Settings** tab under **Security & Compliance**. *** ## Chat agents Chat agents handle text-based conversations across multiple messaging channels. They respond automatically and show all conversations in Unibox. ### Channels Enable the channels you want from the **Settings** tab under **Channels**. | Channel | Status | Description | | --------- | ----------- | ------------------------------------------------- | | SMS | Available | Responds to incoming text messages | | Instagram | Available | Responds to Instagram DMs | | Messenger | Available | Responds to Facebook Page messages | | Email | Available | Responds to inbound emails on a per-agent address | | WhatsApp | Coming soon | — | **SMS** — Select a phone number from your workspace. Each chat agent needs its own SMS number. **Instagram & Messenger** — Select a connected Meta account (Facebook Page with linked Instagram). Connect your account first from **Settings > Integrations > Instagram**. **Email** — Each chat agent gets its own inbound email address on your verified sending domain. Configure it from the **Settings** tab once Email is enabled as a channel. ### Email channel Once Email is enabled, set a per-agent local part (the part before the `@`). Inbound emails sent to that full address are routed to this agent's conversations. | Setting | Description | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Inbound email address | The local part of the address. Combined with your verified sending domain to form the full address (e.g. `support@mail.yourdomain.com`). | **Local part rules:** * Lowercase letters, numbers, dots, hyphens, and underscores only * Max 64 characters * No leading, trailing, or consecutive dots * Must be unique within a workspace — two agents in the same workspace can't share the same local part **Requirements:** * A verified sending domain on the workspace — set up under **Settings > Integrations > Email**. Without one, the inbound address input is hidden because there's no domain to attach to. * The Email channel must be enabled on the agent. **How it works:** * Inbound emails to the agent's address create or continue a conversation in Unibox under that agent. * The agent generates AI replies using its prompt, knowledge base, and configured actions — same engine that powers SMS, Instagram, and Messenger. * Replies sent automatically by the agent or manually from Unibox come **from the agent's own inbound address**, not from a generic notifications address. * Each agent's email conversations are scoped to that agent — two agents on the same workspace can have parallel email threads with the same contact. ### Channel-aware replies Chat agents know which channel each incoming message came from and adjust their replies accordingly. You write one prompt — Nedzo layers channel-specific guidance on top automatically. | Channel | Default tone | Default length | | --------- | ---------------------- | ------------------------------------------------------------ | | SMS | Direct, conversational | 1–2 short sentences (designed to fit a single SMS segment) | | Email | Polished, structured | Multi-paragraph with greeting and sign-off where appropriate | | Web chat | Friendly, helpful | 1–3 sentences, links allowed | | Instagram | Casual, light | Short, can use emoji | | Messenger | Conversational | Short, can use emoji | | WhatsApp | Conversational | Short — optimized for mobile | The same agent can run across multiple channels and stay on-brand on each one — an SMS reply won't be a wall of text, and an email reply won't be a single sentence. Channel-specific instructions you put in the system prompt always take precedence over the defaults. ### Response timing Set a delay (0–60 seconds) before the agent responds. A short delay makes conversations feel more natural. Configure this in the **Settings** tab. ### Manual message behavior When you or anyone on your team replies in a conversation, the agent stops responding on it. This is automatic on every channel and there is nothing to configure — a human reply means a human owns the conversation. Resolving the conversation is what lets the agent answer that contact again. See [AI controls](/unibox/ai-controls) for how this looks in Unibox and for the other pause reasons. ### Message limits Set a maximum number of AI messages per conversation to prevent runaway conversations. When the limit is reached, you can optionally send a final message — for example: *"Thanks for chatting! A team member will follow up with you shortly."* Leave the limit blank for unlimited messages. ### Post-event behavior * **Pause after escalation request** — Stops the agent when a contact asks to speak to a human * **Pause after appointment booked** — Stops the agent after a calendar booking is made * **Idle timeout** — How long to wait before the pause takes effect (60–3600 seconds, default 300) ### Auto-close abandoned conversations Close a conversation automatically when a customer stops replying. Once closed, the conversation moves out of the Open view in Unibox and the same finalization pipeline runs as a manual close: AI summary generation, [Conversation ended](/workflows/triggers/conversation-completed) workflow trigger, [post-conversation webhook](/agents/post-conversation-webhook), and cost rollup. Configure it from the **Settings** tab → **Auto-close abandoned conversations**. | Setting | Default | Description | | ------------------------------------------------ | ------- | ------------------------------------------------------ | | Close conversations when customers stop replying | Off | Master toggle. When off, no auto-close timer is armed. | | Timeout value | 15 | How long to wait for a customer reply before closing. | | Timeout unit | minutes | `minutes`, `hours`, or `days`. | **Presets:** 3, 5, 7, 10, or 15 minutes. Custom values (any positive integer + minutes/hours/days) can be set via the API by writing to `chat_agents.auto_close_timeout_value` and `chat_agents.auto_close_timeout_unit` directly. The Settings UI shows custom values as "Custom: N unit" — picking a preset overwrites the custom value. **How it works:** * When the agent sends an outbound reply, a timer is armed for the configured duration. * A customer inbound message **cancels** the timer — the conversation stays open. * If the timer expires without a customer reply, the conversation closes automatically with `closed_by` set to `null` (system close). * A customer reply on a system-closed conversation **re-opens it** (subject to the reopen window — see below) and the agent resumes responding. Manually-closed conversations (closed by an operator from Unibox) stay closed even if the customer replies — operator decisions are not overridden. **Scope:** * Available on Chat agents only — covers SMS, Instagram, Messenger, and Email channels. * Not available on Voice agents (calls have their own duration limits) or Web Agents. ### Reopen window for closed conversations Control how long a closed conversation can be re-opened by a new contact message before a fresh conversation is started instead. Useful when you want returning customers within a short window to land back in the same thread, but treat anyone returning weeks later as a brand-new conversation. Configure it from the **Settings** tab → **Reopen closed conversations**. | Setting | Default | Description | | --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------ | | Reopen closed conversations | On | Master toggle. When off, every new contact message on a closed conversation starts a brand-new conversation. | | Reopen window | 7 days | How long after the conversation was closed it can still be re-opened. Options: **3, 7, 14, or 30 days**. | **How it works (with auto-close on):** * When a closed conversation receives a new inbound message from the contact, Nedzo checks the time since the conversation was closed. * If reopen is **on** and the conversation was closed within the configured window, the **same conversation re-opens** and the agent resumes responding in it. * If reopen is **off**, or the close happened **outside** the window, a **new conversation** is created instead. * This applies to both system-closed (auto-close) and operator-closed conversations equally — the window is about elapsed time, not who closed it. **Scope:** * Available on Chat agents only. * Independent of the auto-close timeout — the auto-close timeout decides *when* to close, the reopen window decides *for how long* a closed conversation remains reopenable. ### Protection keywords Add keywords that trigger an escalation when a contact uses them. For example: "manager", "human", "complaint". You can add up to 50 keywords. When a contact sends a message containing one of these keywords, the agent pauses and the conversation is flagged for human review. *** ## Web Agent Web Agents let you embed an AI assistant directly on your website. Visitors can chat, talk, or both. ### Widget modes * **Chat** — Text-based conversation. This is the default. * **Voice** — Visitors can speak to your agent through their browser * **Both** — Visitors choose between voice and chat Set the mode in the **Settings** tab. New widgets start in Chat mode, so voice is opt-in. ### Appearance | Setting | Options | Default | | -------- | ---------------------------------------------------------------------------------------- | ------------------ | | Position | Bottom Right, Bottom Center, Bottom Left, Center Right, Center Left, Top Right, Top Left | Bottom Right | | Theme | Light, Dark | Light | | CTA text | Any text (max 25 characters) | "Talk to an agent" | | Avatar | Upload PNG, JPEG, SVG, or WebP (max 2 MB) | Gradient default | ### Branding * **Show branding** — Toggle the branding footer on/off * **Branding text** — Customize the text shown in the footer ### Visitor context The widget automatically captures context about website visitors to give your agent and team more information: * **Page URL** — The page the visitor was on when they started the conversation. This shows up in Unibox so your team can see what the visitor was looking at. * **Name extraction** — The agent extracts the visitor's name from the conversation automatically. Once identified, the contact record is updated with their name. ### Markdown rendering AI responses in the chat widget render basic markdown formatting: * **Bold** and *italic* text * [Hyperlinks](url) — links open in a new tab User-typed messages are displayed as plain text. Markdown rendering works in both the embedded widget and the chat simulator. ### Multilingual support Web Agents respond in the visitor's language automatically. If a visitor writes in Spanish, the agent replies in Spanish — regardless of the language set in the Prompt tab. The language setting on the Prompt tab controls the agent's default language and voice. The multilingual behavior applies to chat mode only. ### Embedding on your site After configuring your widget, copy the script tag from the **Settings** tab and paste it into your site's HTML, just before the closing `` tag. ```html theme={null} ``` The widget loads asynchronously and won't slow down your page. # Create agent Source: https://docs.nedzo.ai/api-reference/agents/create POST /agents Create a new AI agent in a Nedzo workspace via the REST API. Configure the agent type, prompt, voice, language, model, actions, and knowledge base. Create a new agent within a workspace. `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). ## Common Fields Agent name (1-255 characters) Type of agent to create. Valid values: `Voice`, `Chat`, `Widget` Call direction for Voice agents: `inbound` or `outbound` (default: "inbound"). Ignored for Chat and Widget agents. Workspace UUID (required for account API keys, optional for workspace API keys) System prompt for the agent Agent language. Valid values: `english`, `spanish`, `french`, `german`, `portuguese`, `dutch`, `chinese`, `japanese` (default: "english") Whether the agent is active (default: true) ## Type-Specific Fields Opening line the agent says when starting a conversation Voice ID for text-to-speech Enable voicemail detection (default: false). Typically used with outbound direction. Message to leave on voicemail Enable background sound (default: true) When this is enabled, no logs, recordings, or transcriptions will be stored (default: false) Maximum call duration in minutes, 1-60 (default: 30) Voice speed multiplier, 0.5-1.5 (default: 1.0) URL to receive a POST request after each conversation ends. The payload includes the transcript, summary, outcome, duration, contact details, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. **Deprecated.** Use `postConversationWebhookUrl` instead. Still accepted on input for backwards compatibility; responses always use the new field. URL to receive a POST request after each chat conversation ends (SMS, Instagram, Messenger, email, or web chat). The payload includes the transcript, summary, channel, contact details, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. URL to receive a POST request after each web agent conversation ends. The payload includes the transcript, summary, outcome, duration, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/agents" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer Support Agent", "agentType": "Voice", "direction": "inbound", "workspaceId": "123e4567-e89b-12d3-a456-426614174000", "prompt": "You are a helpful customer support assistant.", "openingLine": "Hello! How can I help you today?", "language": "english", "voiceId": "voice_123", "backgroundSound": true, "callDuration": 30, "postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Support Agent", "agentType": "Voice", "direction": "inbound", "prompt": "You are a helpful customer support assistant.", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "english", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # Delete agent Source: https://docs.nedzo.ai/api-reference/agents/delete DELETE /agents/{id} Permanently delete an AI agent by UUID via the Nedzo REST API. Removes the agent and its configuration from the workspace. Cannot be undone. The unique UUID of the Agent ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/agents/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} // 204 No Content ``` # Get agent Source: https://docs.nedzo.ai/api-reference/agents/get GET /agents/{id} Retrieve a single AI agent by UUID via the Nedzo REST API. Returns the agent type, prompt, voice settings, model, actions, and knowledge base. Retrieve a specific agent by its UUID. The unique UUID of the Agent ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/agents/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Support Agent", "agentType": "Voice", "direction": "inbound", "prompt": "You are a helpful customer support assistant.", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "english", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "postConversationWebhookUrl": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # List agents Source: https://docs.nedzo.ai/api-reference/agents/list GET /agents Retrieve all AI agents via the Nedzo REST API. Workspace keys return agents in that workspace; account keys return agents across all workspaces. List agents based on API key scope. Workspace API keys return agents in that workspace only. Account API keys return all agents across all workspaces, grouped by workspace. ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/agents" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Workspace API Key Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Support Agent", "agentType": "Voice", "direction": "inbound", "prompt": "You are a helpful customer support assistant.", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "english", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "postConversationWebhookUrl": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ] ``` ```json Account API Key Response theme={null} [ { "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "workspaceName": "My Workspace", "agents": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Support Agent", "agentType": "Voice", "direction": "inbound", "prompt": "You are a helpful customer support assistant.", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "english", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ] } ] ``` # Update agent Source: https://docs.nedzo.ai/api-reference/agents/update PATCH /agents/{id} Update an existing AI agent by ID via the Nedzo REST API. Modify the agent prompt, voice, language, model, actions, or knowledge base configuration. Update an agent's details. All fields are optional - only provided fields will be updated. ## Path Parameters The unique UUID of the Agent ## Common Fields Agent name (1-255 characters) Whether the agent is active System prompt for the agent Agent language. Valid values: `english`, `spanish`, `french`, `german`, `portuguese`, `dutch`, `chinese`, `japanese` Call direction for Voice agents: `inbound` or `outbound`. Only applicable to Voice agents. ## Type-Specific Fields Opening line the agent says when starting a conversation Voice ID for text-to-speech Enable voicemail detection. Typically used with outbound direction. Message to leave on voicemail Enable background sound When this is enabled, no logs, recordings, or transcriptions will be stored Maximum call duration in minutes, 1-60 Voice speed multiplier, 0.5-1.5 URL to receive a POST request after each conversation ends. The payload includes the transcript, summary, outcome, duration, contact details, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. **Deprecated.** Use `postConversationWebhookUrl` instead. Still accepted on input for backwards compatibility; responses always use the new field. URL to receive a POST request after each chat conversation ends (SMS, Instagram, Messenger, email, or web chat). The payload includes the transcript, summary, channel, contact details, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. URL to receive a POST request after each web agent conversation ends. The payload includes the transcript, summary, outcome, duration, and extracted fields. Set to `null` to disable. See the [Post-conversation webhook](/agents/post-conversation-webhook) page for the full payload. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/agents/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Agent Name", "prompt": "Updated system prompt...", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "spanish", "voicemail": false, "voicemailMessage": "Please leave a message after the tone.", "callDuration": 30, "voiceId": "voice_123", "postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Updated Agent Name", "agentType": "Voice", "direction": "inbound", "prompt": "Updated system prompt...", "isActive": true, "backgroundSound": true, "openingLine": "Hello! How can I help you today?", "language": "spanish", "voicemail": false, "voicemailMessage": null, "hipaaCompliance": false, "callDuration": 30, "speed": 1.0, "voiceId": "voice_123", "postConversationWebhookUrl": "https://example.com/webhooks/conversation-completed", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T11:00:00Z" } ``` # Make a call Source: https://docs.nedzo.ai/api-reference/calls POST /call Initiate an outbound AI voice call via the Nedzo REST API. Specify the agent, phone number, and optional metadata to start an automated conversation. UUID of the agent to use for the call Type of call target. Valid values: `contact`, `phoneNumber` Custom variables to pass to the assistant. These override contact field values. UUID of the contact to call Phone number to call (E.164 format, e.g., +14155551234) First name Last name Email address Business or company name Custom field values by field name. Field names must match existing custom field definitions. The agent must have a phone number assigned in **Agent Builder > Settings** before you can make outbound calls. Calls fail with `400 Bad Request` if the agent has no number connected — Nedzo never falls back to another workspace number, since that would surface the wrong caller ID to the recipient. ## Errors | Status | Detail | Cause | | ------ | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | `400` | `Agent does not have a phone number assigned. Please assign a number in Agent Builder > Settings before making outbound calls.` | The agent has no phone number connected. | | `400` | `Phone number is not synced to voice engine.` | The agent's assigned number hasn't finished syncing to the voice engine yet. | | `400` | `Phone number is not active.` | The agent's assigned number is in `pending` or another non-active status. | | `400` | `Invalid call type` | `type` is something other than `contact` or `phoneNumber`. | ```bash Call Contact theme={null} curl -X POST https://api.nedzo.ai/v1/call \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "123e4567-e89b-12d3-a456-426614174000", "type": "contact", "contactId": "456e4567-e89b-12d3-a456-426614174000" }' ``` ```bash Call Phone Number theme={null} curl -X POST https://api.nedzo.ai/v1/call \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "123e4567-e89b-12d3-a456-426614174000", "type": "phoneNumber", "phoneNumber": "+14155551234", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "businessName": "Acme Inc", "customFields": { "Lead Source": "Website", "Company Size": "50-100" } }' ``` ```json Response theme={null} { "conversationId": "123e4567-e89b-12d3-a456-426614174000" } ``` # Create contact Source: https://docs.nedzo.ai/api-reference/contacts/create POST /contacts Create a new contact in a Nedzo workspace via the REST API. Provide a phone number, name, email, tags, and custom field values to add a contact record. `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). Workspace UUID (required for account API keys, optional for workspace API keys) First name Last name Email address Phone number (E.164 format) IANA timezone identifier (e.g. `America/New_York`, `Europe/Amsterdam`). Used by features that respect the contact's local time, such as workflow scheduling and campaign send windows. * **Optional.** When omitted, the timezone is auto-derived from the phone number (US area code or country code). If the phone can't be resolved to a region, the workspace's default timezone is used as fallback. If neither resolves, the field is left null. * Caller-supplied values always win — if you pass a value, it's stored as-is. * Must be a valid IANA timezone identifier; bogus strings are rejected with `400 Bad Request`. Tag names to attach to the contact. Tags that don't exist yet in your workspace are auto-created. Tag attachment is **additive** — this endpoint never removes a tag from a contact. * Names are matched **case-sensitively** (`"VIP"` and `"vip"` are different tags), trimmed of leading/trailing whitespace, and must be 1–255 characters. * Pass an empty array (`[]`) to no-op (no tags attached, none removed). * Tags in the response are returned in alphabetical order regardless of input order. Custom field values keyed by the custom variable name **as displayed in the dashboard** (e.g. `"Lead Source"` — including spaces and original casing; not a slug). Unknown keys are rejected with a `400`. Define new custom variables on the Contacts page before referencing them via the API. * Accepts `string`, `number`, `boolean`, or `null`. All values are persisted as text and **returned as strings** regardless of input type (e.g. `250` → `"250"`). * Pass `null` to clear a stored value. This endpoint **strictly validates the request body**. Unknown top-level fields are rejected with a `400 Bad Request`. Custom field values must use the `customFields` object — they cannot be added as top-level fields. `phone` and `email` are **unique per workspace** among non-deleted contacts. If another active contact in the same workspace already has the same `phone` or `email`, the request fails with `409 Conflict` and an `errors` array indicating which field collided. Use [`POST /contacts/upsert`](/api-reference/contacts/upsert) instead if you want create-or-update semantics. ```json 409 Conflict theme={null} { "type": "https://api.nedzo.ai/errors/conflict", "title": "Conflict", "status": 409, "detail": "A contact with this phone already exists in this workspace.", "instance": "/v1/contacts", "errors": [ { "field": "phone", "message": "A contact with this phone already exists in this workspace." } ] } ``` ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/contacts" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["newsletter", "vip"], "customFields": { "Lead Source": "Website", "MRR": 250 } }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["newsletter", "vip"], "customFields": { "Lead Source": "Website", "MRR": "250" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # Delete contact Source: https://docs.nedzo.ai/api-reference/contacts/delete DELETE /contacts/{id} Permanently delete a contact by UUID via the Nedzo REST API. Removes the contact record and associated data from your workspace. Cannot be undone. Contact UUID ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/contacts/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} // 204 No Content ``` # Get contact Source: https://docs.nedzo.ai/api-reference/contacts/get GET /contacts/{id} Retrieve a single contact by UUID via the Nedzo REST API. Returns the contact name, phone number, email, tags, metadata, and workspace details. Contact UUID ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/contacts/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["newsletter", "vip"], "customFields": { "Lead Source": "Website", "MRR": "250" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # List contacts Source: https://docs.nedzo.ai/api-reference/contacts/list GET /contacts Retrieve all contacts in a workspace via the Nedzo REST API. Filter by workspace ID with account keys, or list contacts scoped to a workspace key. `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). Workspace UUID (required for account API keys, optional for workspace API keys) ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/contacts?workspaceId=789e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["newsletter", "vip"], "customFields": { "Lead Source": "Website", "MRR": "250" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ] ``` # Update contact Source: https://docs.nedzo.ai/api-reference/contacts/update PATCH /contacts/{id} Update an existing contact by ID via the Nedzo REST API. Modify the contact name, phone number, email, tags, or custom field values with a PATCH request. Contact UUID First name Last name Email address Phone number IANA timezone identifier (e.g. `America/New_York`). Pass `null` to clear the field. Used by features that respect the contact's local time. Must be a valid IANA timezone identifier; bogus strings are rejected with `400 Bad Request`. Tag names to attach to the contact. Tags that don't exist yet in your workspace are auto-created. Tag attachment is **additive** — this endpoint never removes a tag. Names are matched case-sensitively, trimmed, and must be 1–255 characters. Omit the field (or pass `[]`) to leave tags unchanged. Adding a tag here fires the [Contact tagged](/workflows/triggers/contact-tagged) trigger, so any workflow listening for that tag runs. Only tags the contact doesn't already have fire it — re-sending a tag the contact already carries does nothing, so a CRM that re-pushes the same contact on every sync won't re-trigger your workflows. Custom field values keyed by the custom variable name **as displayed in the dashboard** (e.g. `"Lead Source"` — including spaces and original casing; not a slug). Accepts `string`, `number`, `boolean`, or `null` (null clears a stored value). Values you provide overwrite the existing value for that key; other custom fields on the contact are unaffected. Unknown keys are rejected with a `400`. Values are persisted as text and returned as strings. At least one field must be provided. Unknown top-level fields are rejected with `400 Bad Request`. Custom field values must use the `customFields` object — they cannot be added as top-level fields. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/contacts/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "firstName": "Jane", "tags": ["gold"], "customFields": { "MRR": 500 } }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "Jane", "lastName": "Doe", "email": "jane@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["gold", "vip"], "customFields": { "Lead Source": "Website", "MRR": "500" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T11:00:00Z" } ``` # Upsert contact Source: https://docs.nedzo.ai/api-reference/contacts/upsert POST /contacts/upsert Create or update a contact via the Nedzo REST API. Matches existing active contacts by phone first, falling back to email. Create or update a contact based on phone (primary) or email (fallback) matching against active (non-deleted) contacts. Returns `201` for new contacts and `200` for updates. At least one of `phone` or `email` must be provided. Matching only considers active contacts. If the only match is a soft-deleted contact, it is ignored and a brand new contact is created — the deleted record stays deleted with its data intact. By default, on a match, **provided** values in the request overwrite the existing contact's scalar fields and custom field values; fields you omit from the request are left untouched. Set `preserveExisting: true` to merge instead — only empty fields and missing custom field values are filled. **Tags are always additive** regardless of `preserveExisting`. `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). Workspace UUID (required for account API keys, optional for workspace API keys) First name Last name Email address (used for matching if phone is not provided) Phone number in E.164 format (primary matching field) IANA timezone identifier (e.g. `America/New_York`). Used by features that respect the contact's local time. * **Only applied when creating a new contact.** When matching an existing contact, the existing timezone is preserved (or merged per `preserveExisting`); auto-derivation does not run on updates. * On create, when omitted, the timezone is auto-derived from the phone number with the workspace default as fallback. Caller-supplied values always win. * Must be a valid IANA timezone identifier; bogus strings are rejected with `400 Bad Request`. When `true`, populated fields on the matched contact are preserved — only empty fields and missing custom field values are filled in. When `false` (default), the request values overwrite the existing fields and custom field values. Tag attachment is always additive regardless of this flag. Tag names to attach to the contact. Tags that don't exist yet are auto-created in your workspace. Tags are always **additive** — this endpoint never removes a tag from a contact, regardless of `preserveExisting`. Names are matched case-sensitively, trimmed, and must be 1–255 characters. Tags in the response are returned in alphabetical order. Adding a tag fires a workflow trigger. On a **new** contact it fires [Contact created](/workflows/triggers/contact-created) with the tags already attached. On a **matched existing** contact it fires [Contact tagged](/workflows/triggers/contact-tagged), once per tag the contact didn't already have. Re-sending tags the contact already carries fires nothing, so repeated syncs of the same contact won't re-trigger your workflows. Custom field values keyed by the custom variable name **as displayed in the dashboard** (e.g. `"Lead Source"` — including spaces and original casing; not a slug). Accepts `string`, `number`, `boolean`, or `null` (null clears a stored value). Behavior on a match follows `preserveExisting`: when `false` (default), provided values overwrite; when `true`, only fill custom fields the contact doesn't have a value for. Unknown keys are rejected with a `400`. Values are persisted as text and returned as strings. This endpoint **strictly validates the request body**. Unknown top-level fields are rejected with `400 Bad Request`. Custom field values must use the `customFields` object. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/contacts/upsert" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "phone": "+14155551234", "email": "john@example.com", "firstName": "John", "lastName": "Doe", "tags": ["vip"], "customFields": { "Lead Source": "Website", "MRR": 250 } }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+14155551234", "timezone": "America/Los_Angeles", "tags": ["vip"], "customFields": { "Lead Source": "Website", "MRR": "250" }, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # API reference Source: https://docs.nedzo.ai/api-reference/introduction Complete reference for the Nedzo REST API. Covers authentication, base URL, request formats, error codes, pagination, and available endpoints. The Nedzo API is organized around REST. It uses standard HTTP methods, returns JSON responses, and uses standard HTTP status codes. ## Base URL ``` https://api.nedzo.ai/v1 ``` ## Authentication All API requests require a Bearer token in the `Authorization` header: ```bash theme={null} Authorization: Bearer YOUR_API_KEY ``` See [Authentication](/authentication) for more details. If you're using an **account-scoped** API key, most endpoints require a `workspaceId` in the request body or query string. Workspace-scoped keys infer it automatically. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope) for details on which to use and how to find your workspace ID. ## Request Format For `POST`, `PUT`, and `PATCH` requests, send JSON in the request body: ```bash theme={null} curl -X POST https://api.nedzo.ai/v1/agents \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "My Agent", "workspaceId": "789e4567-e89b-12d3-a456-426614174000"}' ``` ## Response Format All responses are JSON. Successful responses return the requested data: ```json theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "My Agent", "createdAt": "2024-01-15T10:30:00Z" } ``` ## Errors Errors follow the [RFC 7807](https://tools.ietf.org/html/rfc7807) Problem Details format: ```json theme={null} { "type": "https://api.nedzo.ai/errors/not-found", "title": "Not Found", "status": 404, "detail": "Agent not found" } ``` ### HTTP Status Codes | Code | Description | | ----- | ------------------------------------------------------------------- | | `200` | Success | | `201` | Created | | `400` | Bad Request - Invalid parameters | | `401` | Unauthorized - Invalid or missing API key | | `404` | Not Found - Resource doesn't exist | | `409` | Conflict - Resource already exists or conflicts with existing state | | `422` | Validation Error - Request failed validation | | `429` | Too Many Requests - Rate limited | | `500` | Internal Server Error | | `502` | Bad Gateway - Upstream service error | ## Pagination List endpoints support pagination via query parameters: | Parameter | Description | Default | | --------- | ------------------------ | ------- | | `limit` | Number of items per page | 20 | | `offset` | Number of items to skip | 0 | ```bash theme={null} GET /v1/contacts?limit=50&offset=100 ``` ## Rate Limiting Write requests on the public API (e.g. creating contacts, sending messages, triggering calls) are throttled per API key using a sustained-plus-burst model: * **Sustained rate:** 1 request per second. * **Burst capacity:** equal to your account's concurrency limit. Requests above the burst capacity are rejected immediately, not queued. When you exceed the limit, you'll receive a `429` response with a `Retry-After` header (in seconds) indicating when you can retry. Read endpoints (`GET`) are not subject to this throttle. If you need a higher burst limit, [contact support](mailto:support@nedzo.ai) or your Nedzo account contact. # Create template Source: https://docs.nedzo.ai/api-reference/templates/create POST /templates Create a new template from an agent, workflow, or entire workspace via the Nedzo REST API. Snapshots configuration for sharing or reuse. Create a new template. Specify `templateType` as `"agent"`, `"workflow"`, or `"workspace"`. For `agent`/`workflow` provide the corresponding `agentId`/`workflowId`. For `workspace`, the entire workspace is snapshotted (all agents, workflows, custom fields, and tags; contacts and conversations are excluded). `workspaceId` is required for **account-scoped** API keys and inferred from the key for **workspace-scoped** keys. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). ## Body Parameters Source workspace. For `agent`/`workflow` templates: the workspace containing the source entity. For `workspace` templates: the workspace to snapshot. Required for account-scoped API keys, inferred from workspace-scoped keys if omitted. Type of template to create. One of `agent`, `workflow`, or `workspace`. Agent ID to create template from. Required when `templateType` is `"agent"`. Workflow ID to create template from. Required when `templateType` is `"workflow"`. Template name (1-255 characters). Template description (max 1000 characters). ```bash Agent theme={null} curl -X POST "https://api.nedzo.ai/v1/templates" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "templateType": "agent", "agentId": "456e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent", "description": "A voice agent template for onboarding" }' ``` ```bash Workflow theme={null} curl -X POST "https://api.nedzo.ai/v1/templates" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "templateType": "workflow", "workflowId": "456e4567-e89b-12d3-a456-426614174000", "name": "Lead Qualification Workflow" }' ``` ```bash Workspace theme={null} curl -X POST "https://api.nedzo.ai/v1/templates" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspaceId": "789e4567-e89b-12d3-a456-426614174000", "templateType": "workspace", "name": "Sales Team Starter", "description": "Full workspace setup — agents, workflows, custom fields, and tags" }' ``` ```json Response (201) theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent", "description": "A voice agent template for onboarding", "templateType": "agent", "visibility": "private", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-15T10:30:00Z", "updatedAt": "2026-01-15T10:30:00Z" } ``` # Delete template Source: https://docs.nedzo.ai/api-reference/templates/delete DELETE /templates/{id} Soft-delete a template by UUID via the Nedzo REST API. The template is hidden from list results but not permanently removed from the database. Soft-delete a template. The template is not permanently removed but will no longer appear in list results. Returns `204 No Content` on success. Returns `404` if the template does not exist or is already deleted. ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```text Response (204) theme={null} No Content ``` # Duplicate template Source: https://docs.nedzo.ai/api-reference/templates/duplicate POST /templates/{id}/duplicate Duplicate an existing template via the Nedzo REST API. The copy is created with private visibility and a "(Copy)" suffix appended to the name. Create a copy of a template. The duplicate gets `" (Copy)"` appended to its name and is always set to private visibility regardless of the original. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000/duplicate" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response (201) theme={null} { "id": "def45678-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent (Copy)", "description": "A voice agent template for onboarding", "templateType": "agent", "visibility": "private", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-16T09:15:00Z", "updatedAt": "2026-01-16T09:15:00Z" } ``` # Get template Source: https://docs.nedzo.ai/api-reference/templates/get GET /templates/{id} Retrieve a template by UUID via the Nedzo REST API. Returns metadata and the full decompressed snapshot including agents, workflows, and tags. Retrieve a template by its UUID. Returns metadata plus the full decompressed snapshot (agents, workflows, custom fields, and tags). ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent", "description": "A voice agent template for onboarding", "templateType": "agent", "visibility": "private", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-15T10:30:00Z", "updatedAt": "2026-01-15T10:30:00Z", "snapshot": { "schemaVersion": "1.0.0", "exportedAt": "2026-01-15T10:30:00Z", "sourcePlatformVersion": "1.0.0", "content": { "agents": [ { "original_id": "456e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding", "agent_type": "Inbound Voice", "prompt": "You are a helpful onboarding assistant...", "opening_line": "Hello! Welcome to our platform.", "language": "english" } ] }, "metadata": { "originalIds": { "456e4567-e89b-12d3-a456-426614174000": "agent" }, "requiredIntegrations": ["ghl"] } } } ``` # Import template Source: https://docs.nedzo.ai/api-reference/templates/import POST /templates/{id}/import Import a public or private template into a workspace via the Nedzo REST API. The import is queued and returns an importId to track the job. Import a template into a workspace. The import is queued for processing and completes quickly; the returned `importId` identifies the job. Public templates can be imported by any account. Private templates can only be imported by the owning account. `workspaceId` is always required for this endpoint — it identifies the target workspace to import into and cannot be inferred from the API key. See [Workspace ID and Request Scope](/authentication#workspace-id-and-request-scope). ## Body Parameters Target workspace to import the template into. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000/import" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"workspaceId": "789e4567-e89b-12d3-a456-426614174000"}' ``` ```json Response (201) theme={null} { "importId": "abc12345-e89b-12d3-a456-426614174000", "status": "queued", "validation": { "valid": true, "itemCounts": { "agents": 1, "workflows": 0, "customFields": 0, "tags": 0 }, "warnings": [], "errors": [] }, "message": "Import queued successfully." } ``` # List templates Source: https://docs.nedzo.ai/api-reference/templates/list GET /templates Retrieve all templates owned by your account via the Nedzo REST API. Filter by template type and visibility, with limit and offset pagination. List templates owned by your account. Supports filtering by `templateType` and `visibility`, with pagination via `limit`/`offset`. ## Query Parameters Filter by template type. One of `agent`, `workflow`, or `workspace`. Filter by visibility. One of `public` or `private`. Maximum number of templates to return (1-100). Number of templates to skip for pagination. ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/templates?templateType=agent&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Customer Onboarding Agent", "description": "A voice agent template for onboarding new customers", "templateType": "agent", "visibility": "private", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-15T10:30:00Z", "updatedAt": "2026-01-15T10:30:00Z" } ] ``` # Update template Source: https://docs.nedzo.ai/api-reference/templates/update PATCH /templates/{id} Update a template's name, description, or visibility setting via the Nedzo REST API. Only the fields you provide in the request body are changed. Update template metadata. Only provided fields will be updated. At least one field must be provided. ## Body Parameters Template name (1-255 characters). Template description. Set to `null` to clear. Template visibility. One of `public` or `private`. Public templates can be previewed without authentication and imported by any account. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/templates/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Template Name", "description": "Updated description", "visibility": "public" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "name": "Updated Template Name", "description": "Updated description", "templateType": "agent", "visibility": "public", "schemaVersion": "1.0.0", "snapshotSizeBytes": 15240, "createdAt": "2026-01-15T10:30:00Z", "updatedAt": "2026-01-16T08:00:00Z" } ``` # Attach payment method Source: https://docs.nedzo.ai/api-reference/workspace-billing/attach-payment-method POST /workspaces/{workspaceId}/billing/payment-methods/attach Attach a Stripe PaymentMethod to a workspace's billing customer via the Nedzo REST API. The first attached method automatically becomes the default. Workspace UUID Stripe PaymentMethod ID (starts with `pm_`), already confirmed against the setup intent on the frontend. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/payment-methods/attach" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "paymentMethodId": "pm_1234567890abcdef" }' ``` ```json Response theme={null} { "paymentMethod": { "id": "pm_1234567890abcdef", "type": "card", "brand": "visa", "last4": "4242", "expMonth": 12, "expYear": 2030, "isDefault": true, "createdAt": "2026-04-17T10:30:00Z" }, "isDefault": true } ``` # Create setup intent Source: https://docs.nedzo.ai/api-reference/workspace-billing/create-setup-intent POST /workspaces/{workspaceId}/billing/payment-methods/setup-intent Create a Stripe SetupIntent for attaching a new payment method to a workspace. Returns a client_secret for collecting card details with Stripe.js. Workspace UUID ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/payment-methods/setup-intent" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "clientSecret": "seti_1234567890_secret_abc123", "customerId": "cus_1234567890abcdef" } ``` # Credit wallet Source: https://docs.nedzo.ai/api-reference/workspace-billing/credit-wallet POST /workspaces/{workspaceId}/billing/wallet/credit Add credits to a workspace wallet as a manual adjustment via the Nedzo REST API. Uses an atomic operation and records the change in the ledger. Workspace UUID Amount to credit in cents. Must be a positive integer. Human-readable reason for the adjustment (1-500 characters). Cannot contain the substring `[idempotency:`. Optional idempotency key (a-z, A-Z, 0-9, hyphen, 1-128 chars). If provided, a retry with the same key within 24 hours returns the existing transaction without re-applying the credit. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/wallet/credit" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 2500, "reason": "Customer goodwill credit", "idempotencyKey": "credit-2026-04-17-abc" }' ``` ```json Response theme={null} { "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "transactionId": "c4e5b6d7-1234-5678-9abc-def012345678", "amount": 2500, "newBalance": 7500, "type": "credit", "reason": "Customer goodwill credit", "createdAt": "2026-04-17T10:30:00Z" } ``` # Debit wallet Source: https://docs.nedzo.ai/api-reference/workspace-billing/debit-wallet POST /workspaces/{workspaceId}/billing/wallet/debit Deduct funds from a workspace wallet as a manual adjustment via the Nedzo REST API. Returns 402 if the balance is insufficient for the debit. Workspace UUID Amount to debit in cents. Must be a positive integer. Human-readable reason for the adjustment (1-500 characters). Cannot contain the substring `[idempotency:`. Optional idempotency key (a-z, A-Z, 0-9, hyphen, 1-128 chars). If provided, a retry with the same key within 24 hours returns the existing transaction without re-applying the debit. ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/wallet/debit" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 1000, "reason": "Chargeback correction", "idempotencyKey": "debit-2026-04-17-xyz" }' ``` ```json Response theme={null} { "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "transactionId": "d5f6a7b8-2345-6789-abcd-ef0123456789", "amount": 1000, "newBalance": 6500, "type": "debit", "reason": "Chargeback correction", "createdAt": "2026-04-17T10:35:00Z" } ``` ```json 402 Insufficient Balance theme={null} { "type": "https://api.nedzo.ai/errors/payment-required", "title": "Payment Required", "status": 402, "detail": "Insufficient wallet balance" } ``` # Get wallet balance Source: https://docs.nedzo.ai/api-reference/workspace-billing/get-balance GET /workspaces/{workspaceId}/billing/balance Retrieve the current wallet balance for a workspace in cents via the Nedzo REST API. Use this endpoint to check available funds before debiting. Workspace UUID ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/balance" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "balance": 5000, "currency": "usd" } ``` # Get usage summary Source: https://docs.nedzo.ai/api-reference/workspace-billing/get-usage GET /workspaces/{workspaceId}/billing/usage Aggregates wallet transactions across a date range and returns total spend, total top-ups, total refunds, net adjustments, and a per-type breakdown. Workspace UUID Inclusive ISO 8601 lower bound on `created_at`. Exclusive ISO 8601 upper bound on `created_at`. ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/usage?startDate=2026-04-01T00:00:00Z&endDate=2026-05-01T00:00:00Z" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "workspaceId": "19c3b12f-ec54-43ad-8686-00e921f1befd", "startDate": "2026-04-01T00:00:00Z", "endDate": "2026-05-01T00:00:00Z", "currency": "usd", "totalSpendCents": 8500, "totalTopupCents": 20000, "totalRefundCents": 0, "totalAdjustmentCents": 1500, "byType": { "usage": -8500, "topup": 20000, "refund": 0, "adjustment": 1500 } } ``` # List payment methods Source: https://docs.nedzo.ai/api-reference/workspace-billing/list-payment-methods GET /workspaces/{workspaceId}/billing/payment-methods List all Stripe payment methods attached to a workspace via the Nedzo REST API. Returns an empty array if no Stripe customer exists yet. Workspace UUID ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/payment-methods" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} [ { "id": "pm_1234567890abcdef", "type": "card", "brand": "visa", "last4": "4242", "expMonth": 12, "expYear": 2030, "isDefault": true, "createdAt": "2026-04-15T10:30:00Z" } ] ``` # List wallet transactions Source: https://docs.nedzo.ai/api-reference/workspace-billing/list-transactions GET /workspaces/{workspaceId}/billing/transactions Retrieve a paginated list of wallet transactions for a workspace. Filter by date range and transaction type for billing reconciliation. Workspace UUID Max transactions to return per page (1-100). Offset for pagination. Inclusive ISO 8601 lower bound on `created_at`. Exclusive ISO 8601 upper bound on `created_at`. Filter by transaction type: `topup`, `usage`, `refund`, or `adjustment`. ```bash cURL theme={null} curl "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd/billing/transactions?limit=20&offset=0&type=adjustment" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "items": [ { "id": "c4e5b6d7-1234-5678-9abc-def012345678", "type": "adjustment", "amount": 2500, "balanceAfter": 7500, "description": "Manual credit: Customer goodwill credit", "createdAt": "2026-04-17T10:30:00Z" }, { "id": "d5f6a7b8-2345-6789-abcd-ef0123456789", "type": "adjustment", "amount": -1000, "balanceAfter": 6500, "description": "Manual debit: Chargeback correction", "createdAt": "2026-04-17T10:35:00Z" } ], "total": 2, "limit": 20, "offset": 0, "hasMore": false } ``` # Create workspace Source: https://docs.nedzo.ai/api-reference/workspaces/create POST /workspaces Create a new workspace in Nedzo via the REST API. Provide a name and optional settings to set up an isolated environment for agents and contacts. Workspace name (1-255 characters) Workspace description (max 1000 characters) IANA timezone identifier (e.g., "America/Los\_Angeles", "Europe/London") Workspace icon URL Primary contact name Primary contact phone number Primary contact email address Street address State or province ZIP or postal code Country code or name Business registration number (EIN, VAT, etc.) ```bash cURL theme={null} curl -X POST "https://api.nedzo.ai/v1/workspaces" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Corporation", "description": "Main workspace for Acme Corp operations", "timezone": "America/Los_Angeles", "icon": "https://example.com/acme-icon.png", "contactName": "Jane Smith", "contactPhone": "+1234567890", "contactEmail": "jane@acme.com", "streetAddress": "123 Main Street", "state": "CA", "zip": "90210", "country": "US", "businessRegistrationNumber": "12-3456789" }' ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "accountId": "789e4567-e89b-12d3-a456-426614174000", "name": "Acme Corporation", "description": "Main workspace for Acme Corp operations", "timezone": "America/Los_Angeles", "icon": "https://example.com/acme-icon.png", "contactName": "Jane Smith", "contactPhone": "+1234567890", "contactEmail": "jane@acme.com", "streetAddress": "123 Main Street", "state": "CA", "zip": "90210", "country": "US", "businessRegistrationNumber": "12-3456789", "balance": 0, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # Delete workspace Source: https://docs.nedzo.ai/api-reference/workspaces/delete DELETE /workspaces/{id} Permanently delete a workspace and all associated agents, contacts, and call data via the Nedzo REST API. This action cannot be undone. Workspace UUID ```bash cURL theme={null} curl -X DELETE "https://api.nedzo.ai/v1/workspaces/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} // 204 No Content ``` # Get workspace Source: https://docs.nedzo.ai/api-reference/workspaces/get GET /workspaces/{id} Retrieve a single workspace by its UUID using the Nedzo REST API. Returns the workspace name, creation date, phone number, and configuration details. Workspace UUID ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/workspaces/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "accountId": "789e4567-e89b-12d3-a456-426614174000", "name": "My Workspace", "description": "Main workspace", "timezone": "America/New_York", "icon": null, "contactName": null, "contactEmail": null, "contactPhone": null, "streetAddress": null, "state": null, "zip": null, "country": null, "businessRegistrationNumber": null, "balance": 0, "isRebilled": false, "rebillingMarkup": 1, "autoTopupEnabled": false, "autoTopupThreshold": null, "autoTopupAmount": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` # List workspaces Source: https://docs.nedzo.ai/api-reference/workspaces/list GET /workspaces Retrieve all workspaces accessible to your API key via the Nedzo REST API. Returns workspace IDs, names, and metadata for scoped keys. ```bash cURL theme={null} curl -X GET "https://api.nedzo.ai/v1/workspaces" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json Response theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "accountId": "789e4567-e89b-12d3-a456-426614174000", "name": "My Workspace", "description": "Main workspace", "timezone": "America/New_York", "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ] ``` # Update workspace Source: https://docs.nedzo.ai/api-reference/workspaces/update PATCH /workspaces/{id} Update an existing workspace by ID via the Nedzo REST API. Change the workspace name, settings, or configuration with a PATCH request to the endpoint. Workspace UUID Workspace name (1-255 characters) Workspace description (max 1000 characters) IANA timezone identifier (e.g., "America/Los\_Angeles", "Europe/London") Workspace icon URL Primary contact name Primary contact phone number Primary contact email address Street address State or province ZIP or postal code Country code or name Business registration number (EIN, VAT, etc.) Enable or disable rebilling on this workspace. When true, the workspace is charged directly via its own Stripe Connect customer. Enabling requires Stripe Connect to be set up on the parent account. Multiplier applied to wallet top-up charges when rebilling is enabled (1 = no markup, 10 = 10x markup). Range 1-10. Enable or disable auto-topup. Enabling requires both `autoTopupThreshold` and `autoTopupAmount` to be set. Balance threshold in cents; when the wallet falls below this, auto-topup triggers. Pass `null` to clear. Amount in cents to charge when auto-topup triggers. Pass `null` to clear. ```bash cURL theme={null} curl -X PATCH "https://api.nedzo.ai/v1/workspaces/19c3b12f-ec54-43ad-8686-00e921f1befd" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Corporation", "description": "Main workspace for Acme Corp operations", "timezone": "America/Los_Angeles", "icon": "https://example.com/acme-icon.png", "contactName": "Jane Smith", "contactPhone": "+1234567890", "contactEmail": "jane@acme.com", "streetAddress": "123 Main Street", "state": "CA", "zip": "90210", "country": "US", "businessRegistrationNumber": "12-3456789" }' ``` ```json Response theme={null} { "id": "19c3b12f-ec54-43ad-8686-00e921f1befd", "accountId": "789e4567-e89b-12d3-a456-426614174000", "name": "Acme Corporation", "description": "Main workspace for Acme Corp operations", "timezone": "America/Los_Angeles", "icon": "https://example.com/acme-icon.png", "contactName": "Jane Smith", "contactPhone": "+1234567890", "contactEmail": "jane@acme.com", "streetAddress": "123 Main Street", "state": "CA", "zip": "90210", "country": "US", "businessRegistrationNumber": "12-3456789", "balance": 0, "isRebilled": false, "rebillingMarkup": 1, "autoTopupEnabled": false, "autoTopupThreshold": null, "autoTopupAmount": null, "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T11:00:00Z" } ``` # Managing Your Subscription Source: https://docs.nedzo.ai/billing/managing-your-subscription Subscribe, upgrade, downgrade, cancel, and handle failed payments for your Nedzo plan. Everything on the Billing page, tab by tab. Your subscription lives in **Settings > Subscription**, split into a **Billing** page and a **Usage** page. This page walks through the Billing page tab by tab. Only workspace **owners and admins** can start, change, or cancel a subscription — other roles see the same tabs read-only. See [Plans & Pricing](/billing/plans) for what each plan includes and how outcomes/qualifications are billed. ## Starting a subscription If your account doesn't have an active plan yet, the **Subscription** tab shows the plan picker: Launch and Scale, side by side, with what's included and the included outcome allowance for each. 1. Click **Get Started** on the plan you want. 2. Nedzo opens Stripe's embedded checkout right in the page — enter your card and confirm. 3. On completion, you're returned to the Subscription tab and your plan is active immediately. Subscribing through the Billing page charges your card right away — there's no free trial on this path. (Enterprise contracts can include a negotiated trial period, set up directly by the Nedzo team.) Enterprise isn't offered here — it's provisioned directly by the Nedzo team. See [Enterprise](/billing/plans#enterprise). ## Buying phone numbers on a trial Buying an additional phone number requires a paid plan. If you're on a trial and open the Buy Number flow, you'll be prompted to upgrade first rather than shown search results. Your free signup number keeps working throughout the trial — this only applies to buying extra numbers. Once you're on a paid plan, purchasing is available immediately. See [phone number rental](/billing/plans#phone-number-rental) for what it costs. ## Changing plans Once you have an active subscription, the Subscription tab shows your current plan with **Change plan** and **Cancel** actions (only between Launch and Scale — Enterprise changes go through your Nedzo contact). **Upgrading** (Launch → Scale) applies immediately: * Your allowance increases right away — usage from that point measures against the new plan's allowance. * You're charged a prorated amount for the rest of the current billing period. * If you're still within a trial, there's no proration charge — you're simply billed the new plan when the trial ends. **Downgrading** (Scale → Launch) applies at the end of your current billing period: * No charge now, and you keep your current (higher) allowance until the period ends. * Your plan switches automatically at the next billing date. ## Canceling and reactivating **Cancel** ends your subscription at the end of the current billing period — you keep access until then, with no further charges after. You can **Reactivate** any time before the period ends to undo the cancellation and stay on your current plan. Once a subscription actually ends (not just scheduled to cancel), Ned features are no longer available for the workspace. ## Payment failures If a scheduled charge fails (expired card, insufficient funds), Nedzo doesn't cut you off immediately: 1. **First failure** — Stripe retries automatically over the following days. Ned keeps working, and the account owner gets an email each time a retry fails. 2. **After the retry schedule is exhausted** (about 14 days) — the subscription is marked unpaid and Ned features are frozen for the workspace until payment succeeds. 3. **Update your card and pay the open invoice** (from the **Payment details** tab, or your Stripe payment email) — Ned access is restored automatically as soon as the payment goes through. ### Paid add-ons pause right away Your **plan** follows the retry schedule above. Paid **add-ons** do not. White-label, reserved concurrent calls, and CPS capacity switch off as soon as the first renewal charge fails, not after the 14-day retry window. | | Plan features | Paid add-ons | | ----------------------------- | ------------- | ------------ | | First failed charge | Keep working | Switch off | | During Stripe's retries | Keep working | Stay off | | Retries exhausted (\~14 days) | Frozen | Stay off | | Payment succeeds | Restored | Restored | If white-label is paused, your branding stops applying and the app falls back to Nedzo's. Reserved concurrent calls and CPS capacity drop back to your plan's normal limits, so a workspace running near its reserved ceiling may see calls queue. Everything comes back on its own the moment the payment goes through — you keep the same add-on, at the same quantity, and there is nothing to re-purchase. Pay the open invoice from the **Payment details** tab and the entitlement is restored automatically. ## Other tabs * **Current period** — Your projected total for the current billing cycle (base plan + usage so far), the date it will bill, and days remaining. * **Invoices** — Every past invoice, downloadable, with amount and status. Rented phone numbers appear as one line per number, so a four-number invoice has four separate lines. * **Payment details** — The card on file (add or replace it) and your billing details (company name, billing address) used on invoices. # Plans & Pricing Source: https://docs.nedzo.ai/billing/plans Nedzo's tiered plans — Launch, Scale, and Enterprise. What's included, how outcome and qualification usage bills, and how telephony and email usage are charged. Nedzo bills on a simple model: a flat monthly plan that includes a set number of AI-handled **outcomes** and **qualifications**, plus usage-based charges for the channels you actually use. Plans are managed from **Settings > Subscription > Billing**. See [Managing your subscription](/billing/managing-your-subscription) for how to subscribe, change plans, and handle payment issues. ## Plans | Plan | Price | Outcomes included | Qualifications included | | -------------- | ----------- | ----------------- | ----------------------- | | **Launch** | \$299/month | 150/month | 10/month | | **Scale** | \$499/month | 250/month | 10/month | | **Enterprise** | Custom | Custom | Custom | Every plan includes unlimited seats. **Launch** gets you live on voice, chat, and email with Ned AI Agent, the unified inbox, and AI knowledge/help desk — everything you need for self-serve onboarding. **Scale** adds WhatsApp and social channels (Instagram, Messenger), workflows and advanced automation, batch calling, warm transfers, and assisted onboarding. **Enterprise** is a custom contract negotiated with sales — custom base fee, allowances, and overage rates, plus the white-label add-on included. It's never available through self-serve checkout; see [Enterprise](#enterprise) below. Launch and Scale are the only plans available through in-app checkout. Enterprise is provisioned directly by the Nedzo team once your contract is signed. ## Outcomes and qualifications Every plan meters two things: | Meter | What it counts | Overage rate | | ------------------ | --------------------------------------------------------------------------------------------------------------- | ------------------------ | | **Outcomes** | A service conversation Ned resolved for your customer (confirmed or assumed resolution, or a completed handoff) | \$1.99 per outcome | | **Qualifications** | A sales lead Ned qualified or disqualified for your team | \$9.99 per qualification | Ned scores every finished conversation automatically — you don't tag or configure anything. A conversation only counts as billable when Ned actually reached a resolution or a qualification decision; conversations with no clear outcome (abandoned, no answer, unresolved) don't count against your allowance. Usage past your plan's included allowance bills automatically at the overage rate above — there's no hard cutoff, and Ned keeps working. Track your usage any time from **Settings > Subscription > Usage**, which breaks down outcomes and qualifications over the current billing period (or any prior period). If your outcome or qualification usage is trending high, set a usage alert from the Usage page so you're notified before it drives up your bill. ## Usage-based charges On top of the monthly plan fee and any outcome/qualification overage, these are billed as you use them and itemized on your invoice each period: | Item | Billed as | | ---------------------------------------- | --------------------------------- | | Call minutes (voice, inbound + outbound) | Per minute | | SMS | Per message | | WhatsApp messages | Per message | | Phone number rental | Flat \$0.99 per number, per month | | Web call minutes (browser-based voice) | Per minute | | Outbound email sends | Per email | | A2P 10DLC registration | Passed through at carrier cost | Call minutes, SMS, WhatsApp, and email rates are usage-based (cost plus a small margin, with a minimum floor per unit). Phone number rental is a flat price, not cost-derived. All of them appear as separate line items on your invoice and are independent of your outcome/qualification allowance. ### Phone number rental The flat \$0.99 applies to local US and Canada numbers, which are the numbers available to buy in the dashboard. Toll-free and premium/non-geographic numbers aren't offered for purchase. Two things to know about how it's charged: * **The first month is prorated.** You pay for the days remaining in the current billing period at purchase, not a full month. The full \$0.99 is charged from the next renewal onward. * **Each number is its own invoice line.** If you rent four numbers you'll see four lines, each labeled with the number, rather than one bundled "phone numbers" charge. Makes it straightforward to see what you're paying for and to reconcile after adding or removing a number mid-period. Every new account also gets one free US number assigned automatically at signup — it carries no rental charge. ## Enterprise Enterprise plans are fully custom: a negotiated monthly (or quarterly/annual) base fee, custom outcome and qualification allowances, and — if negotiated — custom overage rates per meter. The white-label add-on is included at no extra charge. Enterprise accounts don't go through the public plan picker or Embedded Checkout — they're set up directly by the Nedzo team based on your signed contract, including any trial period agreed in the contract. If you're interested in Enterprise, [contact support](mailto:support@nedzo.ai) or your Nedzo account contact. Enterprise usage reporting (per-meter usage vs. allowance, reconciled totals) is available on request for any billing period. # Enterprise SIP Trunk Source: https://docs.nedzo.ai/concepts/enterprise-sip-trunk Enterprise customers can connect their own SIP trunk to Ned to bypass per-minute telephony cost while calls still reach the AI. Enterprise plan only. # Enterprise SIP Trunk The SIP trunk connection is available on the **Enterprise plan only**. On other plans the feature is visible but locked, with a **Contact Sales** button that opens a short in-app form — tell us about your setup and the team follows up. No email needed. Connect your **own SIP trunk** to Ned so your existing telephony carries the calls — bypassing our per-minute PSTN cost — while inbound calls still reach your AI agent exactly as they do today. ## How it works Your SIP trunk sends calls to a dedicated, IP-authenticated SIP connection we provision for your account. That connection is bound to Ned's voice engine, so an inbound trunk call arrives as a normal inbound call and the AI assistant starts on the leg — no change to how your agents behave. ## Prerequisites To connect a SIP trunk you'll need: * **One or more static public IPv4 addresses** for your SIP infrastructure — connections are authenticated by IP allowlist. CIDR ranges and dynamic IPs are not supported; each source must be a single IPv4 address. * **TLS 1.2 or 1.3** for SIP signaling and **SRTP** for media (encrypted end-to-end). * A trunk that supports at least one of the following codecs: **G.722, G.711 µ-law (PCMU), G.711 A-law (PCMA), G.729, Opus**. * You operate your own SIP trunk and carrier; Ned does not resell the underlying telephony. ## What it does — and doesn't — save The savings are on **telephony only** — the per-minute PSTN cost your trunk replaces. The **AI/media** portion of a call (speech-to-text, the model, and text-to-speech) is still billed as usual. SIP is economical for **high-volume** traffic; for low usage a standard number is usually cheaper. ## Requesting it on a non-Enterprise plan Open **Phone Numbers**, find **Enterprise SIP trunk**, and select **Contact Sales**. The form captures your enquiry and files it for the team — you'll get a reply about moving to Enterprise. Submitting again while a request is still open won't create a duplicate. ## Setting it up 1. On the **Enterprise plan**, open **Phone Numbers** and find **Enterprise SIP trunk**. 2. Enter your static source **IPv4 address(es)** (one per line) and select **Connect SIP trunk**. 3. Point your SIP trunk at the connection details we provide and place a test call to confirm it reaches your agent. 4. To change the allowlist later, use **Edit IPs**; to remove the trunk, use **Disconnect**. ## Security * **IP authentication** — only calls from your allowlisted IPs are accepted. * **Encrypted signaling + media** — TLS + SRTP. * Standard anti-toll-fraud protections apply; outbound is rate-limited per source IP. # Cal.com integration Source: https://docs.nedzo.ai/integrations/calcom Connect Cal.com to Nedzo so AI agents can check availability and book meetings during voice calls. Authenticate with an API key and select events. Connect your Cal.com account to let AI agents check your availability and book meetings during calls. ## What it does * **Availability checks** — Agents see your real-time Cal.com availability * **Meeting booking** — Book meetings directly through Cal.com during calls * **Rescheduling** — Move existing bookings to a new time * **Cancellation** — Cancel a booking on request * **Event types** — Agents can reference your different meeting types * **Team event types** — Round-robin and other team events show up alongside your personal ones ## Personal and team event types Nedzo lists both your own event types and the ones belonging to teams you are a member of. Team events are labelled as such, so a round robin shared with your sales team is easy to tell apart from a one-to-one you own. Pick either kind when you set up an agent or a workflow calendar action. Booking a team event follows that team's own assignment rules in Cal.com, so a round robin still rotates across its members. ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **Cal.com** and click **Connect** 3. Sign in with your Cal.com account and authorize access 4. You'll be redirected back to the dashboard once connected ## Using with agents Once connected, enable calendar actions on any agent: 1. Go to your agent's settings 2. Under **Calendar**, select your Cal.com connection 3. The agent can now check availability and book meetings during calls ### Picking a calendar When you open the **Calendar** dropdown in a procedure's *Book a meeting* action, your event types are grouped under two headings: * **Personal** — event types you own * **Team** — event types your team hosts and you have access to Personal always comes first, and the order holds no matter what order Cal.com returns them in. If you have no team event types, the list stays flat with no headings. Which section a calendar sits in doesn't change anything about the booking — picking the same calendar saves the same thing it always did. ## Using in workflows Add a **Calendar Action** node to any workflow to: * Check availability for a time range * Fetch available booking slots * Schedule or reschedule meetings ## Permissions Nedzo requests the following Cal.com permissions: * **Read event types** — To list the meeting types an agent can book * **Read availability** — To find open slots before booking * **Read and write bookings** — To create, reschedule, and cancel appointments * **Read teams and team event types** — To offer your team calendars, such as round-robin and collective meetings ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the Cal.com card You can also revoke access from your Cal.com account settings. # Calendly integration Source: https://docs.nedzo.ai/integrations/calendly Connect Calendly to Nedzo so AI agents can check real-time availability and share scheduling links during voice calls. Uses a personal access token. Connect your Calendly account to let AI agents check your availability and share scheduling links during calls. ## What it does * **Availability checks** — Agents see your real-time Calendly availability * **Scheduling links** — Share your Calendly booking page during conversations * **Event type access** — Agents can reference your different meeting types * **Calling windows** — Avoid calling when your Calendly shows you're booked ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **Calendly** and click **Connect** 3. Sign in with your Calendly account and authorize access 4. You'll be redirected back to the dashboard once connected ## Using with agents Once connected, enable calendar actions on any agent: 1. Go to your agent's settings 2. Under **Calendar**, select your Calendly connection 3. The agent can now check your Calendly availability during calls and offer booking times ### Picking a calendar When you open the **Calendar** dropdown in a procedure's *Book a meeting* action, your event types are grouped under two headings: * **Personal** — event types you own * **Team** — event types your team hosts and you have access to Personal always comes first, and the order holds no matter what order Calendly returns them in. If you have no team event types, the list stays flat with no headings. Which section a calendar sits in doesn't change anything about the booking — picking the same calendar saves the same thing it always did. ## Using in workflows Add a **Calendar Action** node to any workflow to: * Check availability for a time range * Fetch available booking slots * Schedule or reschedule meetings ## Permissions Nedzo requests read access to your Calendly account, including: * **Event types** — To know what meetings you offer * **Availability** — To check when you're free * **Scheduled events** — To avoid double-booking ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the Calendly card You can also revoke access from your [Calendly integrations page](https://calendly.com/integrations). # Email integration Source: https://docs.nedzo.ai/integrations/email Set up a custom email domain in Nedzo to send branded emails from workflows. Configure DNS records, verify your domain, and track replies. Set up a custom email domain to send branded emails from your workflows. Replies are tracked in Unibox. Looking for a support inbox that turns incoming email into Unibox conversations without per-agent setup? See the [Email channel](/integrations/email-channel) instead — it's a separate, simpler setup available on Launch, Scale, and Enterprise plans. ## What it does * **Custom sender domain** — Send emails from your own domain (e.g., `notifications@yourdomain.com`) * **Per-agent inbound addresses** — Give each chat agent its own address on the verified domain (e.g. `support@mail.yourdomain.com`, `billing@mail.yourdomain.com`) so inbound emails route to the right agent * **Workflow emails** — Send automated emails as part of any workflow * **Reply tracking** — Inbound replies appear in Unibox alongside other conversations * **Variable substitution** — Personalize emails with contact data ## How to set up ### 1. Add your domain 1. Go to **Settings > Integrations** in your dashboard 2. Find **Email** and click **Configure** 3. Enter your sending domain (e.g., `mail.yourdomain.com` or `notifications.yourdomain.com`) ### 2. Add DNS records After adding your domain, Nedzo shows you DNS records to add at your domain provider: | Record Type | Purpose | | ----------- | ----------------------------- | | **TXT** | Domain ownership verification | | **CNAME** | Email authentication (DKIM) | | **MX** | Inbound reply handling | Add these records in your domain registrar's DNS settings (GoDaddy, Cloudflare, Namecheap, etc.). ### 3. Verify 1. After adding the DNS records, go back to your dashboard 2. Click **Check** to verify the records 3. DNS propagation can take up to 48 hours, but usually completes within a few minutes Once verified, you're ready to send emails. ## Per-agent inbound addresses Once your domain is verified, each chat agent can claim its own local part on that domain. Open the agent → **Settings** tab → enable the **Email** channel → enter a local part in **Inbound Email Address**. The full inbound address is the local part you set + `@` + your verified sending domain. For a domain `mail.yourdomain.com`, an agent with local part `support` receives emails sent to `support@mail.yourdomain.com`. **Rules:** * Lowercase letters, numbers, dots, hyphens, and underscores * Max 64 characters * No leading, trailing, or consecutive dots * Unique per workspace — two agents in the same workspace can't share the same local part See [Chat agents → Email channel](/agents/voice-agents#email-channel) for full per-agent setup. ## Using in workflows Add a **Send Email** node to any workflow: 1. **Recipient** — Send to the trigger contact's email or enter a custom address 2. **From name** — The sender name that appears in the inbox 3. **Subject** — Email subject line (supports variables) 4. **Body** — Email content (supports variables) **Example:** ``` Subject: Thanks for chatting with us, {{first_name}} Body: Hi {{first_name}}, thanks for your time today. We'll follow up with more details shortly. ``` ## Available variables | Variable | Description | | ---------------- | ---------------------- | | `{{first_name}}` | Contact's first name | | `{{last_name}}` | Contact's last name | | `{{email}}` | Contact's email | | `{{phone}}` | Contact's phone number | Any data passed as trigger variables is also available using `{{variable_name}}`. ## Default domain If you don't set up a custom domain, emails are sent from `nedzo-mail.com`. Setting up your own domain improves deliverability and lets recipients recognize your brand. ## Daily send limits Each workspace has a daily email send cap to protect deliverability and prevent runaway workflows. The cap covers all outbound paths combined — workflow Send Email actions, agent auto-replies, and manual sends from Unibox. When you approach the cap, a banner appears in the dashboard with the current usage and the daily limit. Once the cap is hit, additional sends are blocked for the rest of the UTC day and clearly surfaced as failed in workflow execution logs and the conversation timeline. The counter resets at midnight UTC. If you need a higher limit, contact support. ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Remove** on the Email integration to delete your domain configuration # Email channel Source: https://docs.nedzo.ai/integrations/email-channel Turn on the Email channel to receive support email into Unibox, reply from your own address, and let Ned auto-reply to customers by email. The Email channel gives your workspace a dedicated intake address, so email forwarded from your existing support inbox turns into conversations in Unibox — no per-agent setup required. Available on **Launch, Scale, and Enterprise** plans. This is different from the [Email integration](/integrations/email), which sets up a custom sending domain for **workflow** emails and per-agent inbound addresses. The Email channel is a first-class conversation channel alongside voice, chat, SMS, and WhatsApp — set it up from **Settings > Channels > Email**. ## How it works 1. Nedzo gives your workspace a unique **intake address** (`ws-xxxxxxxxxxxx@nedzo-mail.com`). 2. You forward your real support inbox (e.g. `support@yourcompany.com`) to that intake address using your email provider's admin console. 3. Incoming mail creates or continues a conversation in Unibox, with the true original sender captured as the contact — even through a forwarding rule. 4. You (or Ned, if enabled on the agent) reply from Unibox. Replies go out from your workspace address by default, or from your own domain once you authenticate it. ## Setting it up Go to **Settings > Channels > Email**. ### 1. Enable the channel Click **Enable email channel**. Nedzo provisions your intake address immediately — no configuration needed to start receiving. You can regenerate the intake address at any time (for example, if it leaked). The old address keeps working for **72 hours** after regenerating so you have time to update your forwarding rule, then it stops routing. ### 2. Set your reply email Enter the address your customers actually email and that you'll reply from — e.g. `support@yourcompany.com`. This is the address forwarding rules point away from and toward the intake address. Free/personal email providers (Gmail, Outlook, Yahoo, iCloud, and similar) aren't accepted here — the reply address must be on a domain you control. Changing the reply address later clears any prior forwarding verification, since it's a different mailbox to verify. ### 3. Sender name Set the display name recipients see on emails Ned or your team sends (e.g. "Acme Support"). This is used for every outbound email on the channel and shown next to sent messages in Unibox. ### 4. Set up forwarding Auto-forward your reply address to the intake address using your provider's admin console: **Google Workspace** 1. Sign in to the [Google Admin console](https://admin.google.com) as an administrator. 2. Go to **Apps > Google Workspace > Gmail > Routing**. 3. Add a routing rule for your support mailbox and use **Also deliver to** to add your intake address as an additional recipient. 4. Save. **Microsoft 365** 1. Sign in to the [Exchange admin center](https://admin.exchange.microsoft.com) as an administrator. 2. Go to **Recipients > Mailboxes** and select your support mailbox. 3. Open **Mailbox > Manage mail flow settings > Email forwarding**. 4. Enable forwarding to your intake address, keep a copy of forwarded messages, and save. Forwarding is verified once a one-time token round-trips through your actual forward: a real support email creates a conversation, or Gmail/M365's forwarding-confirmation email is captured automatically (its code or link is stored against your intake address so you don't have to hunt for it). ### 5. Send from your own address (optional) By default, replies send from your workspace intake address. To send from your own address (e.g. `support@yourcompany.com`) instead: 1. Click **Authenticate your domain**. 2. Add the DNS records Nedzo shows you (SPF TXT + DKIM CNAMEs) at your domain host. 3. Click **Validate DNS** once you've added them. DNS changes can take up to 48 hours to propagate. Once verified, replies go out `From:` your real address with proper SPF/DKIM alignment. A domain left unverified for **14 days** is marked failed — start authentication again when you're ready. **DMARC:** Nedzo also shows a recommended DMARC record for your domain and reports whether one is present. This is advisory only — a missing or misconfigured DMARC record never blocks sending, but publishing one improves deliverability and guards against spoofing. ## Replying and AI auto-reply * Replies from Unibox thread correctly (In-Reply-To/References) whether or not your domain is authenticated. * If the agent handling the conversation has AI enabled, Ned can auto-reply to inbound emails on the channel directly, using the same knowledge base, prompt, and actions as your other channels. * Out-of-office auto-replies and mailing-list mail are recognized and never trigger an auto-response loop, but still appear in Unibox for a human to see if needed. Mail that loops back to our own system (or bounce/postmaster chatter) is dropped automatically. ## Suppressions If your own authenticated domain has bounces, spam complaints, or unsubscribes on file with Mailgun, view and clear them from the channel settings (only entries on your own domain — never other workspaces'). A suppressed recipient is blocked with a clear message rather than silently dropped. ## Limits Each workspace has a send-rate limit to protect deliverability; if you hit it, sends are rejected until the limit window resets. If you're consistently hitting it, contact support. # GoHighLevel integration Source: https://docs.nedzo.ai/integrations/gohighlevel Integrate Nedzo with GoHighLevel to trigger AI voice calls from GHL workflows. Sync contacts, map custom fields, and automate outbound calls. Connect GoHighLevel (GHL) to trigger AI voice calls directly from your GHL workflows using the "Make Nedzo Call" action. ## What it does * **Workflow action** — Add a "Make Nedzo Call" step to any GHL workflow * **Agent selection** — Pick which Nedzo agent handles the call from a dropdown * **Contact sync** — GHL contact data is passed to Nedzo automatically * **Two-way sync** — Contact updates in GHL are reflected in Nedzo ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **GoHighLevel** and click **Connect** 3. Sign in with your GHL account 4. Select the GHL location to connect to your Nedzo workspace 5. You'll be redirected back once connected Each GHL location maps to one Nedzo workspace. ## Using in GHL workflows Once connected, a new action becomes available in your GHL workflow builder: ### 1. Add the action 1. Open any workflow in GHL 2. Add a new action step 3. Select **Make Nedzo Call** ### 2. Configure the action | Field | Required | Description | | ------------- | -------- | ----------------------------------------- | | Agent | Yes | Select which Nedzo agent handles the call | | Phone Number | Yes | Contact's phone number (E.164 format) | | First Name | No | Contact's first name | | Last Name | No | Contact's last name | | Email | No | Contact's email address | | Business Name | No | Contact's company name | The **Agent** dropdown automatically loads your Nedzo agents for the connected workspace. ### 3. Map contact fields Use GHL's built-in contact variables to map fields: ``` Phone Number: {{contact.phone}} First Name: {{contact.firstName}} Last Name: {{contact.lastName}} Email: {{contact.email}} Business: {{contact.companyName}} ``` ## Contact sync When a call is triggered from GHL: * If the contact doesn't exist in Nedzo, it's created automatically * If the contact already exists (matched by phone or email), it's updated with the latest GHL data * Custom fields from GHL are synced to Nedzo contact fields ## Authentication The integration uses OAuth — no API keys needed. The GHL `locationId` identifies which workspace to use for each call. ## Error handling | Error | Cause | Fix | | ---------------------- | ---------------------------------------------- | ---------------------------------------------------- | | Location not connected | GHL location isn't linked to a Nedzo workspace | Reconnect from the Nedzo dashboard | | Agent not found | Selected agent was deleted | Update the workflow action to pick a different agent | | Invalid phone number | Phone isn't in E.164 format | Ensure GHL stores phones as `+14155551234` | ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the GoHighLevel card This stops all GHL workflow actions from triggering Nedzo calls for that location. # Google Calendar integration Source: https://docs.nedzo.ai/integrations/google-calendar Connect Google Calendar to Nedzo so AI agents can check real-time availability and book meetings during calls. Supports multiple calendars. Connect your Google Calendar to let your AI agents check your real-time availability and book meetings directly during calls. ## What it does * **Availability checks** — Agents know when you're free or busy before suggesting times * **Meeting booking** — Book meetings on your calendar directly from a call * **Rescheduling** — Move existing bookings to a new time * **Calling windows** — Skip calling contacts when your calendar shows you're busy ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **Google Calendar** and click **Connect** 3. Sign in with your Google account and grant calendar access 4. You'll be redirected back to the dashboard once connected ## Configuration After connecting, you can configure your availability settings: | Setting | Description | Default | | --------------- | --------------------------------- | ------------------- | | Calendar | Which Google Calendar to use | Primary calendar | | Available days | Days of the week you're available | Monday – Friday | | Available hours | Time window for bookings | 9:00 AM – 5:00 PM | | Slot duration | Length of each meeting slot | 30 minutes | | Timezone | Timezone for availability | Your local timezone | These settings are configured per agent, so different agents can use different availability windows. ## Using with agents Once connected, you can enable calendar actions on any agent: 1. Go to your agent's settings 2. Under **Calendar**, select your Google Calendar connection 3. Configure the availability window for that agent 4. The agent will automatically check your calendar during calls ## Using in workflows Add a **Calendar Action** node to any workflow to: * Check availability for a specific date/time * Book a meeting with a contact * Reschedule an existing booking ## Permissions Nedzo requests the following Google Calendar permissions: * **View your calendars** — To list available calendars * **View and edit events** — To check free/busy times and create bookings * **View free/busy information** — To determine availability without reading event details ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the Google Calendar card 3. This removes the connection and any agent calendar configurations tied to it You can also revoke access from your [Google Account permissions page](https://myaccount.google.com/permissions). # Instagram integration Source: https://docs.nedzo.ai/integrations/instagram Connect your Instagram Business or Creator account to Nedzo. Sync DMs into Unibox, respond with AI chat agents, and manage conversations. Connect your Instagram Business or Creator account to sync DMs into Unibox and respond with AI. ## What it does * **DM sync** — Instagram direct messages appear in Unibox alongside calls and other conversations * **AI responses** — Your agents can respond to Instagram DMs automatically * **Unified inbox** — Manage all conversations from one place ## Requirements * An **Instagram Business** or **Instagram Creator** account * A **Facebook Page** linked to your Instagram account * Admin access to the Facebook Page Instagram's API works through Facebook Pages, so you'll connect via Facebook first. ## How to connect ### 1. Connect Facebook 1. Go to **Settings > Integrations** in your dashboard 2. Find **Instagram** and click **Connect** 3. Sign in with your Facebook account 4. Grant access to your Facebook Pages ### 2. Select your page After connecting Facebook, you'll see a list of your Facebook Pages that have a linked Instagram account: 1. Select the page connected to the Instagram account you want to use 2. Click **Connect** 3. Your Instagram DMs will start syncing to Unibox ## How it works Once connected: 1. New Instagram DMs are received via webhook and appear in Unibox 2. You can view and reply to messages directly from the dashboard 3. If an AI agent is configured, it can respond automatically 4. All conversation history is tracked in one thread per contact ## Permissions Nedzo requests Facebook permissions to: * **View your Pages** — To list pages with linked Instagram accounts * **Manage Page messages** — To send and receive Instagram DMs through the Page * **Access Instagram account info** — To identify the linked Instagram account ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the Instagram card This removes the page connection but keeps your Facebook OAuth. To fully disconnect, also remove the Facebook connection. You can revoke app access from your [Facebook Business settings](https://business.facebook.com/settings/apps). # Integrations Source: https://docs.nedzo.ai/integrations/overview Connect Nedzo with Slack, Google Calendar, Calendly, Cal.com, GoHighLevel, Instagram, and email. Sync contacts, trigger workflows, and push call data. Connect Nedzo to the tools you already use. Sync contacts, trigger workflows, and push call data to your CRM, calendar, and more. ## Communication Turn your support inbox into Unibox conversations Send emails from your own domain Sync Instagram DMs to Unibox Send notifications to channels ## Calendars Check availability and schedule calls Open-source scheduling integration Sync availability and book meetings ## CRM Trigger calls from GHL workflows # Slack integration Source: https://docs.nedzo.ai/integrations/slack Connect Slack to Nedzo and send real-time call notifications, summaries, and workflow updates to your team's channels. Set up in minutes with OAuth. Connect Slack to send real-time notifications and call summaries to your team's channels. ## What it does * **Call notifications** — Post updates to a channel when calls complete * **Workflow messages** — Send custom messages to Slack from any workflow * **Variable substitution** — Include contact details and call data in messages * **Channel selection** — Pick which channel to post to per workflow ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **Slack** and click **Connect** 3. Sign in to your Slack workspace and authorize access 4. You'll be redirected back to the dashboard once connected ## Using in workflows Add a **Slack Message** node to any workflow: 1. Select the Slack channel to post to 2. Write your message — use variables like `{{first_name}}`, `{{phone}}`, or any trigger data 3. The message is sent when the workflow reaches that step **Example message:** ``` Call completed with {{first_name}} {{last_name}} ({{phone}}). ``` ## Available variables You can use these variables in your Slack messages: | Variable | Description | | ---------------- | ---------------------- | | `{{first_name}}` | Contact's first name | | `{{last_name}}` | Contact's last name | | `{{email}}` | Contact's email | | `{{phone}}` | Contact's phone number | Any data passed as trigger variables is also available using `{{variable_name}}`. ## Channel access After connecting, Nedzo can post to: * **Public channels** — Nedzo automatically joins the channel when posting * **Private channels** — You need to manually invite the Nedzo app to the channel using `/invite` in Slack ## Token refresh Slack tokens expire periodically. Nedzo automatically refreshes them in the background. If a refresh fails, the integration will show a **Reconnect** prompt in your dashboard — just click it to re-authorize. ## Permissions Nedzo requests the following Slack permissions: * **chat:write** — To post messages to channels * **channels:read** — To list available public channels * **channels:join** — To join public channels when posting * **groups:read** — To list private channels you've invited Nedzo to ## Disconnecting 1. Go to **Settings > Integrations** 2. Click **Disconnect** on the Slack card You can also remove the app from your [Slack workspace settings](https://slack.com/apps/manage). # WhatsApp integration Source: https://docs.nedzo.ai/integrations/whatsapp Connect WhatsApp to Nedzo. Sync messages into Unibox, respond with AI chat agents, and keep using the WhatsApp Business app on the same number with Coexistence. Connect a WhatsApp number to sync messages into Unibox and respond with AI. ## What it does * **Message sync** — WhatsApp messages appear in Unibox alongside calls, email, and other channels * **AI responses** — Your agents can reply to WhatsApp messages automatically * **Coexistence** — Keep using the WhatsApp Business app on your phone with the same number * **History import** — Bring up to about 6 months of past conversations with you when you connect ## Two ways to connect Pick based on whether the number is already in use on a phone. | | Coexistence | New number | | ----------------------------- | ---------------------------------------------------- | --------------------------------- | | **Use it when** | The number already runs in the WhatsApp Business app | The number is not on WhatsApp yet | | **Phone app keeps working** | Yes | No | | **Past conversations import** | Yes, about 6 months | Nothing to import | | **Contacts import** | Yes, from the phone's address book | No | Coexistence is the default. Most businesses connecting to Nedzo already have a number running in the WhatsApp Business app and do not want to give it up. ## Requirements * A **Meta Business account** * A phone number you control * For Coexistence: the number already active in the **WhatsApp Business app**, on a recent version * Admin access to the Meta Business account ## How to connect 1. Go to **Settings > Integrations** in your dashboard 2. Find **WhatsApp** and click **Connect** 3. Sign in with Facebook and pick your Meta Business account 4. Choose the number you want to connect 5. Verify the number when prompted Nedzo opens Meta's own signup flow, so your credentials go to Meta and never to us. ### Verifying the number Meta sends a verification code to the number. Enter it in the **Verify your WhatsApp number** step to finish connecting. ## How Coexistence works Once a Coexistence number is connected, three things flow into Nedzo that a normal connection does not carry: * **History** — past conversations on that number, delivered in chunks over the first few minutes. Older threads appear in Unibox as they arrive. * **Message echoes** — messages your team sends *from the phone app* are copied into Nedzo, so the AI and your operators see the whole thread, not just the half that came through the dashboard. * **Contact sync** — contacts added, changed, or removed in the phone's address book update in Nedzo too. This means someone can answer a customer on their phone and the agent still knows what was said. ## How it works 1. New WhatsApp messages arrive by webhook and appear in Unibox 2. You view and reply from the dashboard, or from the phone app if you use Coexistence 3. If an AI agent is configured, it can reply automatically ## Media WhatsApp conversations support images, video, and audio in both directions. Use the paperclip in the composer to attach files up to 16 MB. See [Unibox channels](/unibox/channels) for the full rules. ## Reply windows WhatsApp enforces its own messaging window. If too much time has passed since the contact's last message, you cannot send a free-form reply until they message again. This applies to media the same way it applies to text. ## Disconnecting Disconnect from **Settings > Integrations**. Meta releases the number on its side, which is not instant — if you try to reconnect right away you may see: > WhatsApp is still releasing this number on Meta's side. Please wait before reconnecting. Wait a few minutes and try again. ## Troubleshooting **The number is already registered elsewhere.** A WhatsApp number can only belong to one Meta Business account at a time. Remove it from the other account first. **History did not import.** History only comes with Coexistence, and only for numbers that were already active in the WhatsApp Business app. A brand-new number has nothing to import. **Messages sent from the phone are missing.** Message echoes are a Coexistence feature. A number connected as a new number has no phone app attached, so there is nothing to echo. # Introduction Source: https://docs.nedzo.ai/introduction Nedzo is an AI customer engagement platform for deploying voice, chat, SMS, and email agents. Explore guides, API references, integrations, and automation. Nedzo is an AI customer engagement platform that deploys AI agents to resolve customer conversations across voice, chat, SMS, and email — all from one platform. This documentation covers everything you need to build, deploy, and refine your AI agents. ## What you can do with Nedzo Build agents with your brand voice and business rules across voice, chat, SMS, and email Automate follow-ups, CRM updates, and notifications based on conversation outcomes Manage all customer conversations across every channel in one inbox Connect your calendar, CRM, Slack, email, and more ## Getting started 1. **Build** — Configure your agents with custom prompts, voices, and business rules 2. **Deploy** — Activate across voice, chat, SMS, and email channels 3. **Refine** — Track metrics and optimize using conversation analytics ## Need help? Join the Nedzo community Contact our support team # AI controls Source: https://docs.nedzo.ai/unibox/ai-controls How a conversation hands off from the AI to a human, why the AI pauses, and how it gets handed back. When a chat agent is handling a conversation, the AI can stop responding — either because a teammate stepped in, or because something about the conversation calls for a person. ## When a teammate replies, the AI stops The moment anyone on your team sends a message in a conversation from Unibox, the AI goes quiet on that conversation. This is automatic on every channel and there is nothing to configure — a human reply means a human owns the conversation. The AI does not come back on that conversation afterwards. When you are finished, **resolve** the conversation with the Resolve button in the thread header. The contact's next message starts fresh and the AI answers it as normal. ## Pause reasons The AI also stops on its own in a few situations. Whatever the cause, the conversation shows a **paused** label with the reason: | Reason | What happened | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | Manual reply | A team member sent a message in the conversation | | Keyword detected | The contact used a protection keyword (e.g. "manager", "human") | | Escalation requested | An escalation ran — the contact asked for a human, or a procedure escalated the conversation. The AI stops for good, every time | | Booking confirmed | A calendar booking was completed | | Message limit reached | The conversation hit the message limit | | Contact opted out | The contact opted out of messages | | Profanity detected | The contact's message tripped the profanity filter | | Stopped by a procedure | A procedure ran a **Stop communication** action — the AI stays silent but the conversation remains open for a person | | Low balance | The workspace wallet ran too low to keep responding | | Usage limit | The workspace hit its plan's usage limit | Where you see it: * A **Paused** chip next to the conversation's status in the thread header, with the full reason on hover. * A row in the conversation timeline marking the moment the AI stopped. ## Turning the AI back on For any pause caused by the conversation itself — a teammate's reply, a keyword, an escalation, a booking, a limit, profanity, a procedure — resolving the conversation is the way back, and deliberately the only one: a paused conversation is one a person is dealing with, so it stays with that person until they close it out. Resolving clears the pause, so the contact's next message is answered by the AI as normal — whether that message reopens this conversation or starts a new one. There is no **Unpause AI** action. A conversation handed to a human is not handed back mid-thread. **Two exceptions clear themselves.** A **Low balance** pause lifts as soon as the wallet is topped up, and a **Usage limit** pause lifts when the limit resets or is raised. Both are workspace-wide billing states rather than a decision about that conversation, so no one has to reopen anything. **Escalations are final.** When a conversation escalates, the AI leaves it and does not come back on its own — not after a follow-up message, and not if the escalation would otherwise trigger again. The contact can keep writing; the AI stays silent and the thread waits for a person. Resolving the conversation is what hands it back. If the same conversation escalates a second time later, the AI stops again, but your team is only notified once, so a reopened thread cannot spam anyone. **Contact opted out is different.** Resolving the conversation does not lift an opt-out. SMS opt-outs block every outbound path to that contact — including the AI — until the contact replies **START**, **UNSTOP**, or **YES**, or someone manually re-opts them in from the contact panel. Resolving just closes this conversation; the AI still won't message that contact again until the opt-out itself clears. ## Configuring pause behavior Some pause reasons are configured per agent, in the agent's **Settings** tab: * Protection keywords that trigger a pause * Profanity detection * Message limits * Whether to pause after a completed booking Two pauses are not configurable. Pausing after a **teammate's reply** always happens, on every channel. Pausing after an **escalation** always happens too: once a conversation has been escalated, the AI is out of it, with no exceptions and no per-agent setting to turn that off. See [Chat Agents](/agents/voice-agents#chat-agents) for full configuration details. # Channels Source: https://docs.nedzo.ai/unibox/channels Learn how voice, SMS, email, Instagram DM, Messenger, and web agent channels work in Unibox. Each channel has its own capabilities. Unibox brings every customer channel into one timeline. Here's how each channel works. ## Voice calls Every inbound and outbound call is logged in Unibox automatically. **What you see:** * Call recording with audio player and waveform * Full transcript with speaker labels * Call metadata: direction, duration, outcome * Cost breakdown: LLM, speech-to-text, text-to-speech, telephony **Outcomes:** Completed, No Answer, Busy, Failed, Voicemail You can play back the recording and read the full transcript directly in the timeline. Click the call to expand details. ## SMS SMS conversations appear as chat bubbles in the timeline. **Sending from Unibox:** * Pick which workspace phone number to send from * Character counter tracks message length and segment count * 160 characters per segment (GSM-7) or 70 characters (Unicode/emoji) * Max 1,600 characters per message **Compliance:** First-time messages to a contact include an opt-out footer automatically. ### Opt-out handling (TCPA) Nedzo enforces SMS opt-outs per `(contact, phone number)`. A contact opted out from one of your numbers is still reachable from another, but every outbound path — Unibox composer, the API, workflows, agent auto-replies, and bulk sends — is blocked once they've opted out from a given number. **Inbound keywords that opt a contact out:** `STOP`, `STOPALL`, `UNSUBSCRIBE`, `CANCEL`, `END`, `QUIT` **Inbound keywords that re-opt a contact in:** `START`, `UNSTOP`, `YES` Keyword matching is case-insensitive and ignores leading/trailing whitespace and punctuation. Carrier-level opt-outs (delivered via Telnyx webhooks) are honored the same way. **In Unibox:** * The contact panel shows an **Opted out** badge on the contact's phone number when an opt-out is on file. Hover for the source (`keyword`, `carrier`, or `manual`) and the date. * The conversation list shows the same badge so operators can spot opted-out contacts before opening the thread. * The composer's send button is disabled with an explanation when the selected workspace number is blocked for that contact. **Manual override:** Click the badge in the contact panel to opt the contact back in manually (records source `manual`). Use this only when you have explicit consent — opting a contact back in without their consent violates TCPA. ## Email Email messages display as cards with From, To, and Subject headers. **Sending from Unibox:** * Add one or more recipients in the "To:" field * Subject is auto-filled as a reply to the existing thread * Larger compose area for longer messages * The sender you see depends on which setup routed the email in — see below There are two ways email reaches Unibox, depending on how your workspace is set up: **Email channel (Launch, Scale, Enterprise)** — Forward your support inbox to your workspace's intake address and conversations land in Unibox automatically, with the true original sender captured as the contact. Replies send from your workspace address by default, or from your own authenticated domain. Ned can auto-reply on these conversations like any other channel. Set up from **Settings > Channels > Email** — see [Email channel](/integrations/email-channel). **Per-agent inbound addresses (custom sending domain)** — Inbound emails are routed to a chat agent by matching the recipient address against that agent's own inbound address (`chat_agents.inbound_email_local_part`), and replies on those threads come from the agent's inbound address so the customer sees a consistent sender across the whole thread. Requires a verified email domain in **Settings > Integrations > Email** — see [Chat agents → Email channel](/agents/voice-agents#email-channel) for setup. ## Instagram Instagram DMs sync to Unibox when you connect your Instagram Business or Creator account. **What you see:** * Contact's Instagram username * Full DM history **Replying:** Instagram enforces a **24-hour reply window**. You can only respond within 24 hours of the customer's last message. If the window expires, the send button is disabled. **Requires:** A connected Facebook Page with a linked Instagram account in **Settings > Integrations > Instagram**. ## Messenger Facebook Messenger conversations sync to Unibox when you connect a Facebook Page. **What you see:** * Contact's Messenger name * Facebook Page the conversation is on * Full message history **Requires:** A connected Facebook Page in **Settings > Integrations > Instagram** (Instagram and Messenger share the same Facebook connection). ## Web Agent Web Agent conversations appear in Unibox when visitors interact with a chat or voice widget embedded on your website. **What you see:** * Chat messages between the visitor and your AI agent * The URL of the page the visitor was on when the conversation started * Contact name (automatically extracted from the conversation) **Requires:** A web agent configured and embedded on your site. See [Web Agent](/agents/voice-agents#web-agent) for setup. ## Attachments & media Email, Instagram, Messenger, and WhatsApp conversations support images, video, and audio in both directions. **Receiving:** Incoming images, videos, and audio are stored and rendered inline in the timeline — images and video play in place, audio gets a player, and other files appear as download links. **Sending:** Use the paperclip in the composer to attach one or more files, preview them as chips, and send (with or without accompanying text). Attachments up to 16 MB are supported. **SMS** does not support media attachments — the composer has no paperclip on SMS, and attachments sent to an SMS thread are rejected. Instagram, Messenger, and WhatsApp still enforce their own reply windows (for example, Instagram's 24-hour window). Media follows the same rules as text — if the window is closed, you can't send. ## Unified timeline When a contact has conversations across multiple channels, everything merges into one chronological timeline. A voice call from Monday, an SMS on Tuesday, and an Instagram DM on Wednesday all appear in order under the same contact. You pick which channel to reply through using the mode dropdown in the composer — the timeline always shows everything together. # Conversations Source: https://docs.nedzo.ai/unibox/conversations View, filter, search, snooze, tag and bulk-manage customer conversations in Unibox. Conversations are grouped by contact across every channel. All conversations are grouped by contact. If a contact has a voice call, an SMS thread, and an Instagram DM, they all appear under one entry. ## Conversation list The left column shows all your conversations with: * **Contact name** (bold if unread) * **Latest message preview** * **Timestamp** (relative — 5m, 2h, 1d) * **Blue dot** for unread conversations * **Unread count badge** ### When the blue dot clears The dot means the contact sent something you have not read yet. * It clears once you open the conversation and the new messages scroll into view. Opening a conversation and never scrolling down to the new messages leaves it unread. * It comes back when the contact sends another message. * A message arriving while you already have that conversation open on screen does not bring it back. You are looking at it. * Replies from your agent or system messages never turn it on. Only messages from the contact do. Reading a conversation does not count as activity, so clearing the dot never bumps a conversation to the top of the list or changes its timestamp. ## Filtering ### Tabs Four lenses sit in a tab row above the list: * **All** — every conversation in the workspace * **Mine** — conversations assigned to you * **Open** — conversations that still need attention * **Snoozed** — conversations you've deferred, with the time each one wakes Tabs are lenses, not access restrictions — **All** shows everyone's conversations. **Mine** shows conversations assigned to you. Unassigned inbound (email arrives with no assignee) appears under **All** until someone claims it. ### Filter popover Open the filter popover for finer control: * **Channel** — a tree covering Call, SMS, Email, WhatsApp, Instagram, Messenger and Web Chat * **Status** — Open, Snoozed, or Closed * **Outcome** — how the conversation actually ended, written by the AI (for example Resolved) * **Assigned to** — a specific teammate, or unassigned * **Created** — a date range **Reset** clears every filter; **Apply** commits them. ### Search Search by contact name, message content, or phone number. Search runs server-side across your full history, including custom field values, and results update as you type. ### Shareable filters Your tab, search term and filters are all kept in the address bar, so the URL you're looking at reproduces the same view for a teammate. ## Managing conversations ### Closing conversations Click **Close** in the thread header when you're finished with a conversation. Closed conversations drop out of **Open**. Status and outcome are two different things. **Status** is where the conversation sits in your queue, and you control it: Open, Snoozed or Closed. **Outcome** is what actually happened, and the AI writes it, for example Resolved. A conversation can be Closed without being Resolved, and that gap is the point: it shows you the ones that ended without the customer getting what they needed. The status pill in the thread header appears only for **Closed** and **Snoozed** conversations — an open conversation is the normal case and needs no label. When the AI has scored an outcome, it shows next to the status. ### Snoozing conversations Snooze a conversation to hide it until you actually want it back. Pick a preset or a custom time: | Preset | Wakes | | ----------- | ---------------- | | Later today | 3 hours from now | | Tomorrow | 9:00 tomorrow | | Next week | 9:00 next Monday | A snoozed conversation returns on its own at the wake time. It also wakes early if the customer replies in the meantime, so a snooze can never hide a waiting customer. ### Tags Add tags from the **Tags** card in the details panel, or to several conversations at once from the bulk console. Tags are workspace-wide, so the same label means the same thing to everyone. ### Bulk actions Tick the checkbox on any row to enter multi-select, or use **Select all** to take everything matching the current filters. A selection bar shows the count and opens the bulk console, where one action applies to the whole selection: * Assign to a teammate * Change status — Open, Snoozed (with a wake time) or Closed * Add or remove tags Selections can mix voice, chat and web conversations — a single request applies the change to all of them. ### Blocking a sender For a persistent unwanted sender, open the thread overflow menu and block them, or manage the list under **Settings → Email blocklist**. You can block a single address or an entire domain, and the block is enforced on every channel. ### Downloading a transcript The thread overflow menu offers **Download transcript**. Message threads download as CSV; voice and web-chat transcripts download as plain text. ### Real-time updates New messages and calls appear instantly — no need to refresh. Unibox uses real-time subscriptions to keep everything up to date. ### Sharing a conversation link Selecting a conversation updates the URL to include the conversation ID: ``` https://app.nedzo.ai/unibox?conversation= ``` This URL is shareable. Pasting it into the browser (or a Slack message, ticket, etc.) opens Unibox with that conversation already selected. If the conversation has moved out of the current view (for example, you selected the **Open** tab but the conversation has since been closed), Unibox still loads it via the deep link. Removing the query parameter or clicking another conversation updates the URL accordingly. ## Timeline The center column shows the full conversation history for a contact, merged across all channels in chronological order. ### Voice calls Each call shows: * **Audio player** with waveform — play, pause, and scrub through the recording * **Call details** (expandable) — direction, duration, outcome, cost breakdown * **Full transcript** (expandable) — every line of the conversation, labeled by speaker ### Messages SMS, email, Instagram, and Messenger messages appear as chat bubbles: * **Outbound messages** — right-aligned, blue background * **Inbound messages** — left-aligned, gray background * **AI responses** — marked with an "AI" badge * **Team member messages** — show the sender's name * **Email messages** — card layout with From, To, and Subject headers ### Delivery status Each outbound message shows its delivery status: | Icon | Status | | ------------------- | --------- | | Spinner | Sending | | Single check | Sent | | Double check (blue) | Delivered | | Double check (pink) | Read | | Red X | Failed | ## Replying Send messages directly from Unibox using the composer at the bottom of the timeline. ### Reply or Note The composer is a single pill with a **Reply / Note** toggle. A reply goes to the customer; a note is internal and only your team sees it. ### Tagging a teammate in a note Type `@` in a note to open a list of your workspace teammates. Keep typing to filter it by name or email, use the arrow keys to move through it, and press Enter or click to insert the tag where your cursor is. Escape closes the list without tagging anyone. You can tag several people in one note. The **Notify teammate** button does the same thing. Everyone you tag gets an email with the note, who wrote it, and a link back to the conversation. You never get an email for tagging yourself. You can only tag people who are members of the workspace. Removing a name from the note before you save it also removes the tag, so nobody is emailed. ### Switching channels The channel pill opens a dropdown of every channel available for that contact — SMS, Email, WhatsApp, Instagram, Messenger, Web Chat or Voice. The composer changes to match: an email reply gets recipient and subject fields, an SMS reply gets a segment counter, and a voice reply offers a call back. Replying to a web-chat conversation uses the **Chat** channel and is delivered to the visitor's chat widget. It is not sent as a text message. ### Draft with Ned **Draft with Ned** in the composer toolbar writes a first draft of your reply from the conversation so far. It fills the composer — nothing is sent until you review and send it yourself. ### Markdown Formatting you write is rendered for the recipient rather than sent as raw symbols, on both email and Slack. ### SMS * Select which phone number to send from * Character counter shows segment count (160 characters per segment) * Max 1,600 characters ### Email * Add recipients in the "To:" field * Supports multiple recipients * Subject auto-filled as a reply to the original thread #### Reply-all (Cc) When an inbound email arrives with other people copied, the composer shows a **reply-all** toggle next to the recipients. Without it a reply only ever reaches the one person who sent the message, so anyone else on the thread never sees the answer. * The toggle only appears when the thread actually carries other participants. On a two-person thread there is nothing to show. * Turning it on copies in the thread's other participants. Each appears as a chip — remove any one with the **✕** on the chip if you don't want them copied. * Replies default to **sender-only**. The toggle resets after every send, and switching conversations clears it, so a Cc list can never carry from one thread to the next. * Up to **25** Cc recipients per reply. Some addresses are dropped before sending, and the reply still goes out to everyone else. When that happens you get a notice naming who was left off — `Reply sent, but not copied to …`. An address is dropped when it is: * **malformed** — not a valid email address * **one of your own channel addresses** — your workspace's inbound address is never copied back onto its own thread * **suppressed** — the address previously bounced or unsubscribed, so it stays suppressed ### Instagram Instagram only allows replies within 24 hours of the customer's last message. If the window has expired, sending is disabled with a notice. ### Keyboard shortcut Press **Cmd+Enter** (Mac) or **Ctrl+Enter** (Windows) to send. ## Contact details Toggle the right column to see contact info: * **Name** — A single field for the contact's full name. The system auto-parses it: the first word is stored as the first name, and the rest as the last name. * Phone number and email * Quick actions: Call, Email * Channel-specific info (Instagram username, Facebook Page, etc.) All fields in the contact panel are editable — click any field to update it. **Custom fields** appear in their own section between Contact Info and Recent Conversations. Every custom field your workspace defines is shown — including empty ones (marked "Not set") — and each is editable inline, so agents can read and update details like a school name or account ID without leaving the conversation. # Unibox Source: https://docs.nedzo.ai/unibox/overview Manage all customer conversations across voice, SMS, email, WhatsApp, Instagram, Messenger and web chat in one unified inbox. Filter, search, snooze, tag and respond. Unibox is your unified inbox for all customer conversations — voice calls, SMS, email, WhatsApp, Instagram DMs, Messenger messages and web chat — all in one place. Every interaction across every channel shows up here, grouped by contact. No switching between tools. ## Layout Unibox is a four-column workspace: 1. **Navigation rail** (far left) — a compact rail for moving between Unibox and the rest of the platform 2. **Conversation list** — your conversations, grouped by Today / Yesterday / Earlier, with tabs for All, Mine, Open and Snoozed 3. **Thread** (center) — the full conversation history for the selected contact, with the composer at the bottom 4. **Details** — contact info, tags and quick actions, or call details on a voice conversation The details column is drag-resizable, and can be collapsed when you want more room for the thread. Below 1200px the layout adapts to the narrower viewport. Unibox supports both light and dark mode, following your system setting. ## What you can do View, filter, search, and manage conversations across all channels How voice calls, SMS, email, Instagram, and Messenger appear in Unibox Pause, resume, and manage AI responses in conversations # Condition action Source: https://docs.nedzo.ai/workflows/actions/condition Split your workflow into different paths using conditional logic. Evaluate contact data, call outcomes, or variables to route each execution. Split your workflow into different paths based on conditions. The workflow evaluates each path's conditions and follows the first one that matches. ## Configuration A condition node has two or more **paths**. Each path has: | Field | Required | Description | | ---------- | -------- | ------------------------------------------------------ | | Label | Yes | A name for this path (e.g., "Interested", "No answer") | | Conditions | Yes | One or more rules to evaluate | | Default | — | One path must be marked as the default fallback | The default path runs when no other path's conditions match. ## Condition rules Each condition evaluates a field against a value using an operator. | Field | Required | Description | | -------- | -------- | ------------------------------------------------------------------------------ | | Field | Yes | The data field to check (contact field, trigger data, or previous step output) | | Operator | Yes | How to compare | | Value | Yes | The value to compare against | ### Available fields * **Contact fields** — `firstName`, `lastName`, `email`, `phone`, `businessName`, `dnc` * **Trigger data** — `tagName`, `channel`, `outcome`, `message`, or any webhook payload field * **Custom fields** — Any custom field on the contact * **Previous step outputs** — Reference outputs from earlier actions using dot notation (e.g., `nodeId.outcome`) #### Do Not Contact (DNC) `dnc` is a boolean field on every contact. It is set to `true` when the contact has opted out (STOP keyword on SMS, unsubscribe link on email, or a manual flag in the contact panel). Branching on DNC lets you suppress outreach for opted-out contacts while still letting the workflow continue down a "log only" path. | Operator | Description | Example | | ---------- | ---------------------------- | ----------------------- | | Equals | DNC has a specific value | `dnc` equals `true` | | Not equals | DNC does not have that value | `dnc` not equals `true` | **Typical pattern:** | Path | Condition | Then | | ------- | ------------------- | ---------------------------------- | | DNC | `dnc` equals `true` | Slack message to #compliance, end | | Default | — | Send SMS / Send Email / Voice call | ### Operators | Operator | Description | Example | | ------------ | ------------------- | ------------------------------- | | Equals | Exact match | outcome equals "completed" | | Not equals | Does not match | outcome not equals "voicemail" | | Contains | Text includes value | message contains "pricing" | | Not contains | Text excludes value | email not contains "spam" | | Greater than | Numeric comparison | durationSeconds greater than 60 | | Less than | Numeric comparison | durationSeconds less than 10 | | Starts with | Text begins with | phone starts with "+1" | | Ends with | Text ends with | email ends with "@gmail.com" | | Is empty | Field has no value | email is empty | | Is not empty | Field has a value | phone is not empty | #### Date operators Date-typed custom fields (see [Contacts → Custom fields](/concepts/contacts#field-types)) support a separate operator set that compares calendar dates rather than string values. | Operator | Description | Example | | ---------- | --------------------------------------------- | ---------------------------------------------------- | | Before | The field's date is earlier than the value | renewalDate before `2026-06-01` | | After | The field's date is later than the value | renewalDate after `2026-01-01` | | Equals | The field's date is exactly the value | birthday equals `2026-05-02` | | In Between | The field's date is within an inclusive range | renewalDate in between `2026-04-01` and `2026-04-30` | Dates are compared in `YYYY-MM-DD` form. The same operators are also available in the Contacts page filter bar. ## Example **Route by call outcome:** Trigger: Conversation ended → Agent Type: voice | Path | Condition | Then | | ---------- | --------------------------- | ------------------------- | | Interested | outcome equals "completed" | Send follow-up email | | Voicemail | outcome equals "voicemail" | Wait 4 hours → Retry call | | No answer | outcome equals "no\_answer" | Send SMS | | Default | — | Add "needs-review" tag | **Route by channel:** Trigger: Contact replied | Path | Condition | Then | | ------------ | -------------------------- | ----------------------------- | | SMS reply | channel equals "sms" | Slack message to #sms-replies | | Instagram DM | channel equals "instagram" | Slack message to #social | | Default | — | Slack message to #general | # Workflow actions Source: https://docs.nedzo.ai/workflows/actions/overview Explore the actions available in Nedzo workflows, including voice calls, SMS, email, Slack messages, webhooks, contact updates, conditions, and wait steps. Actions are the steps in your workflow that do something — make a call, send a message, update a contact, or call an external API. Add as many actions as you need, and they execute in order. ## Variables Use `{{variable}}` syntax in any text field across all actions. | Variable | Description | | ------------------ | ---------------------- | | `{{firstName}}` | Contact's first name | | `{{lastName}}` | Contact's last name | | `{{email}}` | Contact's email | | `{{phone}}` | Contact's phone number | | `{{businessName}}` | Contact's company name | Trigger data and previous step outputs are also available. For example, `{{nodeId.status}}` references the output of a previous action. ## Retries All actions retry up to 3 times with exponential backoff on transient failures (network timeouts, rate limits). Permanent failures (invalid config, auth errors) fail immediately. # Send email action Source: https://docs.nedzo.ai/workflows/actions/send-email Send an email from a custom domain or the default Nedzo domain as a workflow step. Supports dynamic variables, HTML, and reply tracking. Send an email as part of your workflow. Emails are sent from your custom domain or the default `nedzo-mail.com` domain. Replies are tracked in Unibox. ## Configuration | Field | Required | Default | Description | | -------------- | ---------------------- | ----------------- | ----------------------------------------------------------------------------------------------- | | Recipient type | Yes | Contact | **Contact** sends to the trigger contact's email. **Custom** lets you enter a specific address. | | Custom email | If custom | — | Email address. Only shown when recipient type is "Custom". | | From name | No | Workspace default | The sender name that appears in the recipient's inbox. | | Subject | Yes | — | Email subject line. Supports `{{variables}}`. Max 998 characters. | | Body | Yes (Fixed mode) | — | Email content. Supports `{{variables}}`. Max 25,600 characters. Only used in Fixed mode. | | Instruction | No (AI Generated mode) | — | Instructions for the AI when composing the email. Only shown in AI Generated mode. | ### Body mode The Send Email action supports two modes for composing the email body: | Mode | Description | | ---------------- | ----------------------------------------------------------------------------------- | | **Fixed** | You write the exact email content. Supports `{{variables}}` for personalization. | | **AI Generated** | The AI composes the email body based on conversation context and your instructions. | **Fixed mode** is the default. You write the full email body in the Body field with variable support. **AI Generated mode** lets the AI write the email. The AI uses the conversation context (call transcript, previous steps, contact data) to compose a relevant email. Add an **Instruction** to guide what the AI should write — for example, *"Summarize the key points discussed in the call and include next steps"* or *"Write a follow-up thanking them for their time"*. When using AI Generated mode, the Instruction field is optional but recommended. Without it, the AI composes the email based on conversation context alone. With it, you get more control over tone, content, and structure. ## Variables Personalize the subject and body with variables: ``` Subject: Thanks for your time, {{firstName}} Body: Hi {{firstName}}, Thanks for chatting with us today. As discussed, I'm sending over the details for {{businessName}}. Let me know if you have any questions. ``` Available variables: `{{firstName}}`, `{{lastName}}`, `{{email}}`, `{{phone}}`, `{{businessName}}`, plus any trigger data or previous step outputs. ## Custom domain If you've set up a custom email domain in **Settings > Integrations > Email**, emails are sent from that domain. The domain must be DNS-verified before emails can be sent. Without a custom domain, emails are sent from `nedzo-mail.com`. Setting up your own domain improves deliverability and brand recognition. ## Reply tracking When a contact replies to a workflow email, the reply appears in Unibox under that contact's conversation. An inbound webhook route is automatically configured for your domain. ## Example **Send a summary after a call:** 1. Trigger: Conversation ended → Agent Type: voice 2. Condition: Outcome equals "completed" 3. Action: Send email * Recipient: Contact * From name: Your Company * Subject: *"Summary of our conversation"* * Body: *"Hi `{{firstName}}`, thanks for taking the time to speak with us. Here's a quick recap of what we discussed..."* # Send SMS action Source: https://docs.nedzo.ai/workflows/actions/send-sms Send a text message to a contact or custom phone number as a workflow step. Messages appear in Unibox alongside other conversations for unified tracking. Send an SMS message as part of your workflow. Messages appear in Unibox alongside other conversations. ## Configuration | Field | Required | Default | Description | | -------------- | --------- | ------- | ------------------------------------------------------------------------------------------------ | | Phone number | Yes | — | Which workspace phone number to send from. Select from your purchased numbers. | | Recipient type | Yes | Contact | **Contact** sends to the trigger contact's phone. **Custom** lets you enter a specific number. | | Custom phone | If custom | — | Phone number in E.164 format (e.g., `+14155551234`). Only shown when recipient type is "Custom". | | Message | Yes | — | The message text. Supports `{{variables}}`. Max 1,600 characters. | ## Variables Use variables to personalize the message: ``` Hi {{firstName}}, thanks for chatting with us today. We'll send over the details to {{email}} shortly. ``` Available variables: `{{firstName}}`, `{{lastName}}`, `{{email}}`, `{{phone}}`, `{{businessName}}`, plus any trigger data or previous step outputs. ## Compliance The first SMS sent to a new phone number automatically includes an opt-out footer with your workspace info and instructions to reply STOP. This is required for compliance and happens automatically — you don't need to add it to your message. Two independent toggles control this, under **Phone Numbers → Compliance**: the company/sender name and the opt-out notice. Both are **on by default**. You can disable either one per workspace (for example, if your carrier already appends opt-out language), but leaving them on is strongly recommended — opt-out text is generally an A2P/carrier requirement and turning it off makes compliance your responsibility. ### Opt-out enforcement If the contact replied `STOP` (or any of `STOPALL`, `UNSUBSCRIBE`, `CANCEL`, `END`, `QUIT`) to the workspace number you're sending from, the action fails with an opt-out error and the SMS is not sent. The check is per `(contact, phone number)` — a contact opted out from one number can still be reached from another. A contact who later replies `START`, `UNSTOP`, or `YES` is opted back in automatically and the workflow can send again. Carrier-level opt-outs (received via Telnyx webhooks) are enforced the same way. See [Unibox → SMS opt-out handling](/unibox/channels#opt-out-handling-tcpa) for full opt-out behavior. ## Limits | Limit | Value | | ----------------------- | --------------------------------------------- | | Max message length | 1,600 characters | | Daily SMS per workspace | 100 | | Segment length | 160 chars (GSM-7) or 70 chars (Unicode/emoji) | Messages longer than one segment are sent as multi-part SMS and count as multiple segments. ## Checks Before sending, the action verifies: * The contact has a phone number * The contact is not on the Do Not Call (DNC) list * The contact has not opted out from the selected workspace number * The phone number is active in your workspace * The daily SMS limit hasn't been reached If any check fails, the action fails with a descriptive error. ## Example **Follow-up after a call:** 1. Trigger: Conversation ended → Agent Type: voice 2. Condition: Outcome equals "completed" 3. Action: Send SMS * Phone number: Your workspace number * Recipient: Contact * Message: *"Hi `{{firstName}}`, great speaking with you! As discussed, here's the link to get started: [https://example.com/signup](https://example.com/signup)"* # Send webhook action Source: https://docs.nedzo.ai/workflows/actions/send-webhook Send an HTTP request to any external URL from your workflow. Connect Nedzo to CRMs, analytics platforms, automation tools, or custom backends via webhooks. Send an HTTP request to any URL. Use this to connect Nedzo to services that don't have a native integration — CRMs, analytics tools, automation platforms, or your own backend. ## Configuration | Field | Required | Default | Description | | ------- | -------- | ------- | ------------------------------------------------------------------------ | | URL | Yes | — | The endpoint to send the request to. Supports `{{variables}}`. | | Method | Yes | POST | HTTP method: GET, POST, PUT, PATCH, or DELETE. | | Headers | No | — | Custom headers as key-value pairs. Supports `{{variables}}` in values. | | Body | No | — | Request body for POST, PUT, PATCH, and DELETE. Supports `{{variables}}`. | ## Variables Use variables in the URL, headers, and body: ```json theme={null} { "contact_name": "{{firstName}} {{lastName}}", "phone": "{{phone}}", "email": "{{email}}", "call_outcome": "{{outcome}}" } ``` When the body is valid JSON, variables are substituted in a JSON-safe way (special characters are properly escaped). ## Output | Field | Description | | ------------ | ----------------------------- | | `statusCode` | The HTTP response status code | | `body` | The response body (max 1 MB) | | `headers` | Response headers | The response data is available to subsequent steps. For example, if the webhook returns `{"orderId": "123"}`, you can reference `{{nodeId.body.orderId}}` in later actions. ## Example: Send call data to a CRM ```json theme={null} { "method": "POST", "url": "https://your-crm.com/api/calls", "headers": { "Authorization": "Bearer your-api-key", "Content-Type": "application/json" }, "body": { "contact_name": "{{firstName}} {{lastName}}", "phone": "{{phone}}", "email": "{{email}}", "outcome": "{{outcome}}" } } ``` ## Common use cases * **CRM updates** — Push call outcomes to Salesforce, HubSpot, or any CRM with an API * **Zapier / Make** — Trigger Zapier Zaps or Make scenarios via webhook URL * **n8n workflows** — Trigger n8n automations from Nedzo * **Custom logging** — Send conversation data to your own analytics backend * **Slack alternatives** — Post to any service that accepts incoming webhooks ## Limits | Limit | Value | | ------------------ | ----------------------- | | Request timeout | 30 seconds | | Max response size | 1 MB | | Retries on failure | 3 (exponential backoff) | 5xx, 408, and 429 responses are automatically retried. 4xx responses fail immediately. ## Security * Only `http://` and `https://` URLs are allowed * Requests to private/internal IP addresses are blocked (localhost, 10.x.x.x, 172.16.x.x, 192.168.x.x, etc.) * Always use HTTPS for endpoints that require authentication # Slack message action Source: https://docs.nedzo.ai/workflows/actions/slack-message Send a message to a Slack channel from your workflow. Notify your team about call outcomes, new leads, workflow events, or any data using dynamic variables. Send a message to a Slack channel as part of your workflow. Use this to notify your team about call outcomes, new leads, or any workflow event. ## Configuration | Field | Required | Description | | ------- | -------- | --------------------------------------------------------------------------- | | Channel | Yes | The Slack channel to post to. Select from a dropdown of available channels. | | Message | Yes | The message text. Supports `{{variables}}`. Max 2,000 characters. | ## Variables Include contact and workflow data in your message: ``` New conversation completed with {{firstName}} {{lastName}} ({{phone}}). Outcome: {{outcome}} ``` Available variables: `{{firstName}}`, `{{lastName}}`, `{{email}}`, `{{phone}}`, `{{businessName}}`, plus any trigger data or previous step outputs. ## Channel access * **Public channels** — Nedzo automatically joins the channel when posting. * **Private channels** — You need to manually invite the Nedzo app to the channel using `/invite` in Slack before it can post. ## Token refresh Slack tokens expire periodically. Nedzo refreshes them automatically. If a refresh fails, the action fails and the Slack integration shows a **Reconnect** prompt in your dashboard. ## Requires Slack must be connected in **Settings > Integrations > Slack**. ## Example **Notify sales on hot lead:** 1. Trigger: Contact tag added → "hot-lead" 2. Action: Slack message * Channel: #sales * Message: *"🔥 Hot lead: `{{firstName}}` `{{lastName}}` (`{{phone}}`) just tagged as hot lead. Follow up ASAP."* **Post call summary:** 1. Trigger: Conversation ended → Agent Type: voice 2. Action: Slack message * Channel: #call-updates * Message: *"Call with `{{firstName}}` `{{lastName}}` — `{{outcome}}`. Duration: `{{durationSeconds}}`s."* # Update contact action Source: https://docs.nedzo.ai/workflows/actions/update-contact Create or update a contact record within a workflow. Match contacts by phone or email, then set fields like name, tags, and custom metadata automatically. Create a new contact or update an existing one. The action matches contacts by phone number or email — if a match is found, it updates the record. If not, it creates a new contact. ## Configuration | Field | Required | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------- | | First name | No | Contact's first name. Supports `{{variables}}`. | | Last name | No | Contact's last name. Supports `{{variables}}`. | | Phone | No | Phone number in E.164 format (e.g., `+14155551234`). | | Email | No | Email address. Automatically lowercased. | | Business name | No | Company name. Supports `{{variables}}`. | | Add Tags | No | Tags to add to the contact. Tags already on the contact stay. Tags not listed here are untouched. | | Remove Tags | No | Tags to remove from the contact. Tags not listed here are untouched. | | Custom fields | No | Additional fields to set as key-value pairs. | At least one identifying field (phone or email) should be provided so the action can match or create the contact. ## How matching works 1. If a **phone number** is provided, the action searches for an existing contact with that number 2. If no phone match, it searches by **email** 3. If a match is found, the contact is **updated** with the new data 4. If no match, a new contact is **created** Fields you leave blank are not overwritten on existing contacts. ## Output | Field | Description | | ----------- | ----------------------------------------- | | `contactId` | The contact's ID (new or existing) | | `created` | `true` if a new contact was created | | `updated` | `true` if an existing contact was updated | ## Example **Tag contacts after a completed call:** 1. Trigger: Conversation ended → Agent Type: voice 2. Condition: Outcome equals "completed" 3. Action: Update contact * Add Tags: "called", "interested" **Create contact from webhook data:** 1. Trigger: Webhook (receives form submission data) 2. Action: Update contact * First name: `{{firstName}}` * Last name: `{{lastName}}` * Email: `{{email}}` * Phone: `{{phone}}` * Add Tags: "website-lead" 3. Action: Voice call with sales agent **Move a contact between segments:** 1. Trigger: Booking confirmed 2. Action: Update contact * Add Tags: "customer" * Remove Tags: "lead", "interested" # Voice call action Source: https://docs.nedzo.ai/workflows/actions/voice-call Make an outbound AI voice call as a workflow step. Select an agent, configure caller ID and retry settings, and let the agent handle the conversation for you. Make an outbound phone call using one of your AI voice agents. The agent handles the conversation automatically based on its prompt and configuration. ## Configuration | Field | Required | Default | Description | | ------------------ | -------- | --------- | ---------------------------------------------------------------------------------------------------------------------------- | | Agent | Yes | — | Which voice agent handles the call. Only outbound voice agents are shown. | | No-answer behavior | No | None | What happens when the contact doesn't pick up: **None** (stop), **Retry** (call again), or **Continue** (move to next step). | | Voicemail behavior | No | None | What happens when the call goes to voicemail: **None**, **Retry**, or **Continue**. | | Max follow-ups | No | 0 | How many times to retry on no-answer or voicemail. Up to 15 attempts. | | Follow-up delay | No | — | Wait time between retries. Set a number and unit (hours or days). | | Calling days | No | All days | Restrict calls to specific days of the week (Mon–Sun). | | Calling hours | No | All hours | Time window for calls, e.g., 9 AM – 8 PM. Uses the contact's timezone. | ## How follow-ups work When a call results in no-answer or voicemail, the configured behavior determines what happens next: * **None** — The workflow stops at this step. No retry, no next step. * **Retry** — The workflow schedules another call after the follow-up delay. This repeats up to the max follow-ups limit. * **Continue** — The workflow skips the retry and moves to the next action in the flow. Follow-ups respect calling days and hours. If a retry is scheduled outside the calling window, it's deferred to the next available time. ## Calling window and contact timezone Calling days and hours are evaluated in the **contact's** timezone, not your workspace timezone — so a 9 AM – 8 PM window means 9 AM – 8 PM where the contact is. Nedzo resolves the timezone from the contact's phone number. If it can't determine one, the call is **not placed** — it's held rather than dialed against a UTC guess, which is what previously produced calls in the middle of the night. To fix a held contact, set its timezone on the contact record or correct the phone number. ## Output After the call completes, the following data is available to subsequent steps: | Field | Description | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `callId` | The call's unique ID | | `status` | Call status | | `callDisposition` | The [disposition category](/agents/call-analysis#call-disposition) assigned by call analysis (e.g., Interested, Appointment Booked, Not Interested) | | `dataExtracted` | All [extraction fields](/agents/call-analysis#data-extraction) configured on the agent (e.g., budget, timeline) | | `recordingUrl` | URL to the call recording | `dataExtracted` requires extraction fields to be configured on the agent's **Settings > Conversation Analysis** tab. `callDisposition` replaces the previous "Call Outcome" output field. ## Examples **Outreach with follow-up:** 1. Action: Voice call with sales agent * No-answer: Retry after 4 hours * Voicemail: Continue to next step * Max follow-ups: 3 * Calling hours: 9 AM – 6 PM * Calling days: Mon–Fri 2. Action: Send SMS — *"Hi `{{firstName}}`, I tried to reach you. Let me know a good time to chat."* ## Timeout Voice calls have a 5-minute timeout by default. If the call hasn't connected within that time, it's marked as failed. # Wait action Source: https://docs.nedzo.ai/workflows/actions/wait Pause a workflow for a fixed duration or until a specific date and time. Use delays to space out follow-ups, throttle messages, or schedule future actions. Pause the workflow before continuing to the next step. Use delays to time your follow-ups, avoid sending messages too quickly, or schedule actions for specific days. ## Delay types ### Duration Wait for a fixed amount of time. | Field | Required | Description | | ------ | -------- | ----------------------- | | Amount | Yes | How long to wait | | Unit | Yes | Minutes, hours, or days | **Ranges:** 1 minute minimum, 60 days maximum. **Example:** Wait 2 hours after a call before sending a follow-up SMS. ### Until day Wait until a specific day of the week and time. | Field | Required | Description | | ----- | -------- | --------------------------- | | Day | Yes | Monday through Sunday | | Time | Yes | Time of day (e.g., 9:00 AM) | If the specified day/time has already passed this week, it waits until next week. **Example:** Wait until Monday at 9:00 AM to start outbound calls for the week. ### Until date Wait until a specific calendar date and time. | Field | Required | Description | | ----- | -------- | ----------------- | | Date | Yes | A specific date | | Time | Yes | Time on that date | **Example:** Wait until a product launch date before sending announcement emails. ## Timezone All delay types are timezone-aware based on your browser's timezone at the time you configure the delay. The exact resume timestamp is calculated and stored, so the workflow resumes at the correct time regardless of daylight saving changes. ## During the wait While a workflow is waiting: * The execution status shows as **Waiting** * The scheduled resume time is recorded * The workflow automatically resumes when the wait period ends ## Example **Drip sequence:** 1. Trigger: Contact created 2. Action: Send email — Welcome message 3. Action: Wait 1 day 4. Action: Send email — Getting started guide 5. Action: Wait 3 days 6. Action: Voice call with onboarding agent # Workflow executions Source: https://docs.nedzo.ai/workflows/executions Monitor, inspect, and troubleshoot workflow runs. View execution statuses, step-level details, variable values, and error logs for every workflow execution. Every time a workflow runs, it creates an execution record. Use executions to monitor what's happening and troubleshoot issues. ## History range The execution history view has a range selector to control how far back you can see runs. | Range | Availability | | ------------- | -------------------- | | Last 24 hours | All plans | | Last 7 days | All plans | | Last 30 days | Enterprise plan only | On non-Enterprise plans the 30-day option is shown but locked behind an upgrade prompt. Existing execution records older than your range are not deleted — they just aren't surfaced in the dropdown selection. ## Execution statuses | Status | Description | | --------- | -------------------------------------------------------------------------------------------------------------------- | | Pending | Queued, waiting to start | | Running | Currently executing steps | | Waiting | Paused on a delay node | | Completed | Finished successfully | | Failed | An error occurred | | Cancelled | Stopped manually, or automatically because the workflow was deactivated (shown with a "Workflow deactivated" reason) | ## What's tracked Each execution records: * **Trigger type and data** — What started the workflow and the data it carried * **Current position** — Which node is executing (or waiting) * **Step outputs** — The result of each completed step * **Error details** — What went wrong if a step failed * **Timestamps** — When it started, completed, or failed ## Step outputs Every action stores its output after running. These outputs are available to later steps as variables. For example, a voice call step stores: * Call ID * Status and call disposition * Extracted data fields * Recording URL A webhook step stores: * HTTP status code * Response body ## Testing Individual steps can be tested from within their configuration panel. Step-level testing lets you verify each action works correctly before publishing. Test executions are flagged separately and don't count against limits. ## Troubleshooting ### Workflow didn't trigger * Check that the workflow is **published** and the **activate/deactivate toggle** is set to active * Verify the trigger conditions match the event * Check deduplication — the same trigger for the same contact within the dedup window (default 1 minute) is ignored ### Step failed * Check the error message in the execution details * Common causes: missing integration connection, invalid phone/email, expired OAuth token * Transient failures (network issues, rate limits) are retried automatically up to 3 times ### Workflow stuck on "Waiting" The workflow is paused on a delay node. It will resume automatically at the scheduled time. # Workflows Source: https://docs.nedzo.ai/workflows/overview Automate multi-step sequences in Nedzo with triggers, actions, conditions, and delays. Build workflows that react to calls, contacts, and webhooks. Workflows let you automate what happens when something occurs in your workspace — a call ends, a contact is created, a tag is added, or a webhook fires. Chain triggers, actions, conditions, and delays together in a visual builder. ## How it works 1. **A trigger fires** — something happens (a call completes, a contact replies, etc.) 2. **Actions execute in order** — send an SMS, make a call, update a contact, post to Slack 3. **Conditions branch the flow** — take different paths based on data 4. **Delays pause when needed** — wait minutes, hours, or until a specific day ## Building workflows The workflow builder is a visual, node-based editor. Add a trigger, then drag actions, conditions, and delays into the flow. Each node has its own configuration panel. Workflows can be **published** and then **activated** or **deactivated** using the toggle in the editor toolbar (next to the Publish button). This lets you enable or disable a workflow without leaving the editor or deleting it. Deactivating a workflow stops it immediately: no new contacts are enrolled, and all in-progress executions are cancelled — including queued actions, delays, and scheduled follow-up calls. Cancelled executions appear in the execution history with a "Workflow deactivated" reason. A call that is already connected when you deactivate completes normally, but no new calls or messages are started. Reactivating the workflow does not resume cancelled executions — only new enrollments run. ## Learn more Events that start a workflow What your workflow can do Branching logic and timing controls Monitoring and troubleshooting runs # Contact created trigger Source: https://docs.nedzo.ai/workflows/triggers/contact-created Start a workflow when a new contact is added to your workspace, whether created manually, via the API, synced from GoHighLevel, or from an inbound call. Fires whenever a new contact is added to your workspace. This includes contacts created manually in the dashboard, imported via the API, synced from GoHighLevel, or created automatically by an incoming call or message. ## When it fires The trigger fires once per new contact, immediately after the contact record is created. It does not fire when an existing contact is updated. ## Configuration ### Filters Narrow down which new contacts should trigger the workflow. Click **Add Filter** to add conditions. | Filter field | Operators | Description | | ------------ | ------------------------------------------------------------------ | ---------------------------------------------------- | | Email | equals, not equals, contains, not contains, is empty, is not empty | Match against the contact's email address | | Phone | equals, not equals, contains, not contains, is empty, is not empty | Match against the contact's phone number | | Has tag | equals, not equals | Check if the contact was created with a specific tag | | DNC | equals, not equals | Check if the contact is on the Do Not Call list | All filters use AND logic — every filter must match for the workflow to run. **No filters:** If you don't add any filters, the workflow runs for every new contact. ### Examples **Only contacts with an email:** * Filter: Email → is not empty **Only contacts with a US phone number:** * Filter: Phone → starts with → `+1` **Exclude DNC contacts:** * Filter: DNC → not equals → `true` ## Data available When this trigger fires, the following data is available as variables in your workflow: | Variable | Description | Example | | ------------------ | ---------------------------- | ------------------- | | `{{contactId}}` | The contact's unique ID | `a1b2c3d4-e5f6-...` | | `{{firstName}}` | First name | `John` | | `{{lastName}}` | Last name | `Doe` | | `{{phone}}` | Phone number in E.164 format | `+14155551234` | | `{{email}}` | Email address | `john@example.com` | | `{{businessName}}` | Company name | `Acme Inc` | ## Deduplication Uses the contact ID as the dedup key. Default window: 1 minute. This means if the same contact is somehow created twice rapidly (e.g., race condition from two API calls), the workflow only runs once. ## Use case examples ### Welcome call to new leads A new contact is added → wait 5 minutes for them to settle in → call with your sales agent. 1. **Trigger:** Contact created 2. **Action:** [Wait](/workflows/actions/wait) — 5 minutes 3. **Action:** [Voice call](/workflows/actions/voice-call) — Sales agent ### Sync new contacts to your CRM Every new contact is pushed to your external CRM via webhook. 1. **Trigger:** Contact created 2. **Action:** [Send webhook](/workflows/actions/send-webhook) — POST to your CRM API ```json theme={null} { "name": "{{firstName}} {{lastName}}", "phone": "{{phone}}", "email": "{{email}}", "source": "nedzo" } ``` ### Welcome SMS Send a personalized text the moment a contact is created. 1. **Trigger:** Contact created 2. **Action:** [Send SMS](/workflows/actions/send-sms) * Message: *"Hi `{{firstName}}`, thanks for reaching out! We'll be in touch shortly."* # Contact replied trigger Source: https://docs.nedzo.ai/workflows/triggers/contact-replied Trigger a workflow when a contact sends an inbound message via SMS, WhatsApp, Email, Instagram, Messenger, or voice. Automate follow-up actions on replies. Fires when a contact sends a message through any connected messaging channel. Use this to react to inbound SMS, WhatsApp, Email, Instagram DMs, Messenger messages, or voice transcripts. ## When it fires The trigger fires immediately when a contact sends an inbound message through: * **SMS** — An incoming text message to one of your workspace phone numbers * **WhatsApp** — An incoming WhatsApp message to a connected number * **Voice** — A transcribed turn from a contact during a voice call * **Instagram** — A direct message on your connected Instagram account * **Messenger** — A message on your connected Facebook Page * **Email** — An incoming email to one of your agent's inbound email addresses The trigger does **not** fire when a chat conversation is finalized (use [Conversation Ended](/workflows/triggers/conversation-completed) for that) or for outbound messages sent by your team or AI. ## Configuration ### Filters | Filter field | Operators | Description | | ------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | | Reply channel | sms, whatsapp, voice, instagram, messenger, email | Only fire when the reply comes from a specific channel. Select the channel directly — no value field needed. | | Message | equals, not equals, contains, not contains, is empty, is not empty | Match against the message content. Comparisons are case-insensitive. | All filters use AND logic. ### Examples **Only SMS replies:** * Filter: Reply channel → sms **Instagram messages containing "pricing":** * Filter: Reply channel → instagram * Filter: Message → contains → `pricing` **Any reply with content (not empty):** * Filter: Message → is not empty **Any reply from any channel:** * No filters. Leave it empty. ## Data available | Variable | Description | Example | | --------------------------- | --------------------------------- | --------------------- | | `{{contactId}}` | The contact's ID | `a1b2c3d4-...` | | `{{firstName}}` | Contact's first name | `John` | | `{{lastName}}` | Contact's last name | `Doe` | | `{{phone}}` | Contact's phone number | `+14155551234` | | `{{email}}` | Contact's email | `john@example.com` | | `{{trigger.reply.message}}` | The message text the contact sent | `Yes, I'm interested` | | `{{trigger.reply.channel}}` | Which channel the reply came from | `sms` | ## Deduplication Uses the contact ID + conversation ID as the dedup key. Default window: 1 minute. If a contact sends multiple messages quickly in the same conversation, only the first one triggers the workflow. ## Use case examples ### Notify sales on any SMS reply Every inbound SMS gets posted to Slack so the team can follow up. 1. **Trigger:** Contact replied → Reply channel: sms 2. **Action:** [Slack message](/workflows/actions/slack-message) — #sales channel * Message: *"SMS from `{{firstName}}` `{{lastName}}` (`{{phone}}`): `{{trigger.reply.message}}`"* ### Auto-tag engaged contacts When a contact replies on any channel, mark them as engaged. 1. **Trigger:** Contact replied (no filters) 2. **Action:** [Update contact](/workflows/actions/update-contact) — Add "engaged" tag ### Route by channel Different channels get different handling. 1. **Trigger:** Contact replied (no filters) 2. **Action:** [Condition](/workflows/actions/condition) * **Path: SMS** — `{{trigger.reply.channel}}` equals `sms` → [Slack message](/workflows/actions/slack-message) to #sms-replies * **Path: Instagram** — `{{trigger.reply.channel}}` equals `instagram` → [Slack message](/workflows/actions/slack-message) to #social * **Default** → [Slack message](/workflows/actions/slack-message) to #general ### Re-engage after reply When a contact replies, schedule a follow-up call. 1. **Trigger:** Contact replied → any channel 2. **Action:** [Update contact](/workflows/actions/update-contact) — Add "engaged" tag 3. **Action:** [Wait](/workflows/actions/wait) — 10 minutes 4. **Action:** [Voice call](/workflows/actions/voice-call) — Follow-up agent # Contact tag trigger Source: https://docs.nedzo.ai/workflows/triggers/contact-tagged Start a workflow when a tag is added to or removed from a contact. Launch campaigns, onboarding sequences, or status-change automations. Fires when a tag is added to or removed from a contact. This is one of the most flexible triggers — use it to start campaigns, run onboarding sequences, or react to status changes. ## When it fires The trigger fires immediately when: * A tag is **added** to a contact (manually, via API, or from another workflow) * A tag is **removed** from a contact You configure whether the workflow responds to additions, removals, or both. ### Adding tags through the API Tags added through the REST API fire this trigger the same way a dashboard tag does: | Endpoint | What fires | | --------------------------------------------------------- | -------------------------------------------------------------------------------------- | | [`POST /contacts`](/api-reference/contacts/create) | [Contact created](/workflows/triggers/contact-created), with the tags already attached | | [`PATCH /contacts/{id}`](/api-reference/contacts/update) | **Contact tagged**, once per newly added tag | | [`POST /contacts/upsert`](/api-reference/contacts/upsert) | **Contact tagged** on a matched contact; Contact created on a new one | Only tags the contact doesn't already have fire the trigger. If your CRM re-pushes the same contact and tags on every sync, the repeat tags fire nothing — so a follow-up workflow won't run twice on the same contact. Bulk contact imports deliberately don't fire this trigger. Importing a tagged list won't start a workflow for every row. ## Configuration ### Tag event Choose the event type: | Event | Description | | ----------- | ------------------------------------------ | | Tag Added | Fires when a tag is added to a contact | | Tag Removed | Fires when a tag is removed from a contact | You can create separate workflows for additions and removals, or use a single workflow with a [Condition](/workflows/actions/condition) to branch. ### Tag filter Select which tags should trigger the workflow: | Setting | Description | | ------------- | ----------------------------------------------------------------------------------------------------------- | | Specific tags | Select one or more tags from the dropdown. The workflow only fires when one of these tags is added/removed. | | Any tag | Leave the tag filter empty. The workflow fires on any tag change. | ### Examples **Only fire on "hot-lead" tag:** * Event: Tag Added * Tag: Select "hot-lead" from dropdown **Fire when any tag is removed:** * Event: Tag Removed * Tag: Leave empty (matches all) **Fire on multiple tags:** * Event: Tag Added * Tags: Select "interested", "qualified", "ready-to-buy" ## Data available | Variable | Description | Example | | ---------------------- | ------------------------------- | ------------------ | | `{{contactId}}` | The contact's ID | `a1b2c3d4-...` | | `{{firstName}}` | Contact's first name | `John` | | `{{lastName}}` | Contact's last name | `Doe` | | `{{phone}}` | Contact's phone number | `+14155551234` | | `{{email}}` | Contact's email | `john@example.com` | | `{{trigger.tag.id}}` | The tag's ID | `t1a2b3c4-...` | | `{{trigger.tag.name}}` | The tag's name | `hot-lead` | | `{{trigger.tag.mode}}` | Whether it was added or removed | `added` | ## Deduplication Uses the contact ID + tag ID as the dedup key. Default window: 1 minute. If the same tag is toggled on and off quickly, only the first event triggers the workflow. ## Use case examples ### Start outreach when tagged "hot lead" A sales rep tags a contact → the agent calls them automatically. 1. **Trigger:** Contact tagged → Tag Added → "hot-lead" 2. **Action:** [Voice call](/workflows/actions/voice-call) — Sales agent 3. **Action:** [Condition](/workflows/actions/condition) — Check call outcome * **Outcome = no\_answer:** [Send SMS](/workflows/actions/send-sms) — *"Hi `{{firstName}}`, I just tried to reach you. When's a good time to chat?"* * **Outcome = voicemail:** [Wait](/workflows/actions/wait) 4 hours → [Voice call](/workflows/actions/voice-call) retry * **Default:** [Update contact](/workflows/actions/update-contact) — Add "contacted" tag ### Onboarding drip sequence New customer tagged → send a series of emails over several days. 1. **Trigger:** Contact tagged → Tag Added → "new-customer" 2. **Action:** [Send email](/workflows/actions/send-email) — Welcome email 3. **Action:** [Wait](/workflows/actions/wait) — 1 day 4. **Action:** [Send email](/workflows/actions/send-email) — Getting started guide 5. **Action:** [Wait](/workflows/actions/wait) — 3 days 6. **Action:** [Voice call](/workflows/actions/voice-call) — Onboarding check-in agent ### Notify team when tag removed When a VIP tag is removed, alert the team. 1. **Trigger:** Contact tagged → Tag Removed → "vip" 2. **Action:** [Slack message](/workflows/actions/slack-message) — *"`{{firstName}}` `{{lastName}}` is no longer a VIP. Tag was removed."* 3. **Action:** [Send webhook](/workflows/actions/send-webhook) — Update CRM status # Conversation ended trigger Source: https://docs.nedzo.ai/workflows/triggers/conversation-completed Trigger a workflow when an AI conversation ends — voice calls, chat on any messaging channel, or web agent sessions. Access transcript and summary. Fires when any AI conversation ends — voice calls, chat conversations on any connected messaging channel, or web agent sessions. This is one of the most powerful triggers — it gives you access to the full conversation data including transcript, summary, disposition, and any extracted fields. This trigger was previously called **Call Completed** (voice-only) and **Conversation Completed**. Existing workflows continue to work unchanged. The internal type identifier is still `conversationCompleted`. ## When it fires The trigger fires immediately after a conversation ends: | Agent type | Fires after | | ---------- | ---------------------------------------------------------------------------------------------------------------- | | Voice | A phone call ends, regardless of outcome (answered, voicemail, no answer, or failed). Both inbound and outbound. | | Chat | An SMS, WhatsApp, Instagram DM, Messenger, email, or web chat conversation is finalized. | | Web | A web agent session (voice or chat widget embedded on your site) ends. | ## Configuration ### Filters Use filters to only trigger the workflow for specific conversations. All filters use AND logic. | Filter field | Options | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Agent Type | voice, chat, web (multi-select) | Only fire for the selected agent types. Leave empty to fire for all types. | | AI Agent | Select agents | Only fire for conversations handled by specific agents | | Call direction | Inbound, Outbound | **Voice only.** Only fire for calls in a specific direction | | Call disposition | Free-text with `is` / `is not` / `contains` operators | Match against the AI-classified disposition label on the conversation. Case-insensitive. | | Min duration | Number (seconds) | **Voice only.** Only fire if the call lasted at least this long | | Max duration | Number (seconds) | **Voice only.** Only fire if the call was shorter than this | | Extracted data | Free-text per extraction field with `equals` / `not equals` / `contains` / `not contains` / `is empty` / `is not empty` / `greater than` / `less than` operators | Match against any extraction field configured on the agent (budget, lead status, appointment date, etc.). Available across voice, chat, and web. See [Extracted data filters](#extracted-data-filters) below. | Voice-only filters (direction, duration) only apply when Agent Type includes voice. If you select only `chat` or `web`, those filters are ignored. The Call disposition and Extracted data filters work on every channel. ### Call disposition Disposition is a free-text label generated by the AI at the end of every conversation, based on the agent's disposition prompt. Because the prompt produces arbitrary strings (`Hot Lead`, `Spanish Speaker — Transferred`, `Wrong Person`, etc.), the filter is a free-text input rather than a fixed dropdown. | Operator | Behavior | | ---------- | ------------------------------------- | | `is` | Exact match, case-insensitive | | `is not` | Negated exact match, case-insensitive | | `contains` | Substring match, case-insensitive | Existing workflows configured with the legacy outcome dropdown values (`completed`, `positive`, `appointment_booked`, etc.) keep working — they're treated as exact-match `is` filters against the stored value. For voice-specific routing on raw call ending reasons (voicemail, no answer, transferred, etc.), use a [Condition](/workflows/actions/condition) step on `{{trigger.conversation.endedReason}}` — see the voice-only fields table further down. ### Extracted data filters Filter on any extraction field configured on the agent — budget, lead status, appointment date, sentiment, anything you've set up. The filter picker shows every extraction field defined on the agents your workflow listens to, and works across voice, chat, and web conversations. | Operator | Behavior | | ---------------------------- | ----------------------------------------------------------------------------------------------------------- | | `equals` / `not equals` | Exact match, case-insensitive. Booleans and numbers are compared as strings (`true`, `42`). | | `contains` / `not contains` | Substring match, case-insensitive. | | `is empty` / `is not empty` | Matches when the AI didn't extract a value (missing key, null, empty string, empty array, or empty object). | | `greater than` / `less than` | Numeric comparison. Both sides must be numeric — non-numeric values never match. | Behavior notes: * Filtering only fires the workflow when the field matches. Filters are AND-combined — every filter must match. * `not equals` and `not contains` against a missing value match (vacuous truth) — useful for "fire unless the AI said X". * Extracted values are also available as variables in your actions — see [`{{trigger.conversation.metadata.fieldName}}`](#conversation-variables) further down. ### Examples **Only completed outbound voice calls:** * Filter: Agent Type → voice * Filter: Call direction → Outbound * Filter: Call disposition → `is` `completed` **All chat conversations from a specific agent:** * Filter: Agent Type → chat * Filter: AI Agent → Select your support agent **Voice or web sessions longer than 30 seconds:** * Filter: Agent Type → voice, web * Filter: Min duration → 30 **Every conversation on every channel:** * No filters. Leave it empty. **Hot leads only (any channel):** * Filter: Extracted data → `lead_status` `contains` `hot` **Conversations where appointment date was captured:** * Filter: Extracted data → `appointment_date` `is not empty` **High-budget leads:** * Filter: Extracted data → `budget` `greater than` `5000` ## Data available This trigger provides the richest data of any trigger type. Some fields are only present for certain agent types — use the Agent Type filter or a Condition step to branch safely. ### Contact variables Available for every conversation regardless of channel. | Variable | Legacy alias | Description | Example | | ----------------------- | --------------- | ---------------------- | ------------------ | | `{{contact.contactId}}` | `{{contactId}}` | The contact's ID | `a1b2c3d4-...` | | `{{contact.firstName}}` | `{{firstName}}` | Contact's first name | `John` | | `{{contact.lastName}}` | `{{lastName}}` | Contact's last name | `Doe` | | `{{contact.phone}}` | `{{phone}}` | Contact's phone number | `+14155551234` | | `{{contact.email}}` | `{{email}}` | Contact's email | `john@example.com` | Dot notation (`{{contact.firstName}}`) is now the standard. The flat aliases on the right still work for backwards compatibility — existing workflows continue to render unchanged. ### Conversation variables The canonical namespace for all channels. Use these in new workflows. | Variable | Description | Example | | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `{{trigger.conversation.conversationId}}` | Unique conversation ID | `0a9b8c7d-...` | | `{{trigger.conversation.channel}}` | Channel the conversation happened on | `voice`, `sms`, `whatsapp`, `instagram`, `messenger`, `email`, `web_chat`, `web` | | `{{trigger.conversation.agentType}}` | Which agent type handled it | `voice`, `chat`, or `web` | | `{{trigger.conversation.agentId}}` | Agent that handled the conversation | `x1y2z3-...` | | `{{trigger.conversation.agentName}}` | Agent's name | `Sales Agent` | | `{{trigger.conversation.startedAt}}` | When it started (ISO 8601) | `2026-04-23T14:30:00Z` | | `{{trigger.conversation.endedAt}}` | When it ended (ISO 8601) | `2026-04-23T14:34:05Z` | | `{{trigger.conversation.summary}}` | AI-generated summary | `Discussed pricing...` | | `{{trigger.conversation.transcript}}` | Full transcript (text-channel lines are timestamp-prefixed in the workspace timezone, e.g. `[2026-06-15 14:23:07] User: Hi...`; voice/web lines are not) | `[2026-06-15 14:23:07] User: Hi...` | | `{{trigger.conversation.disposition}}` | AI-classified disposition label (when enabled on the agent) | `Interested - Demo Scheduled` | | `{{trigger.conversation.metadata}}` | Custom extraction fields | `{"budget": "5000"}` | Extraction fields configured on the agent (budget, timeline, etc.) are available under `{{trigger.conversation.metadata.fieldName}}`. ### Voice-only fields Present when `agentType` is `voice`. | Variable | Description | Example | | ------------------------------------------ | -------------------------------- | --------------------- | | `{{trigger.conversation.callId}}` | Voice Engine call ID | `c1d2e3f4-...` | | `{{trigger.conversation.duration}}` | Call length in seconds | `245` | | `{{trigger.conversation.direction}}` | Inbound or outbound | `outbound` | | `{{trigger.conversation.endedReason}}` | Raw reason the call ended | `customer-ended-call` | | `{{trigger.conversation.appointmentDate}}` | Booked appointment date (if any) | `2026-04-28` | The legacy `outcome` field has been removed from the trigger payload. Use `{{trigger.conversation.disposition}}` for the AI-classified outcome label, or `{{trigger.conversation.endedReason}}` for the raw call ending reason. ### Chat-only fields Present when `agentType` is `chat`. | Variable | Description | Example | | ---------------------------------------- | ----------------------------------------- | ---------------------- | | `{{trigger.conversation.messageCount}}` | Number of messages exchanged | `8` | | `{{trigger.conversation.lastMessageAt}}` | Timestamp of the final message (ISO 8601) | `2026-04-23T14:45:00Z` | Chat conversations do not include `direction` or `duration` — use `disposition` and timestamps instead. ### Web-only fields Present when `agentType` is `web`. Web sessions use the voice-style fields (`callId`, `duration`, `endedReason`) plus `channel: "web"`. ### Legacy `trigger.call.*` namespace For backwards compatibility, existing voice workflows can still use the `{{trigger.call.*}}` namespace. It maps 1:1 to `{{trigger.conversation.*}}` for voice-only fields (`callId`, `duration`, `direction`, `summary`, `transcript`, `agentId`, `agentName`, `agentType`, `startedAt`, `endedAt`, `endedReason`, `appointmentDate`, `metadata`). New workflows should use `{{trigger.conversation.*}}` since the legacy namespace only resolves for voice conversations. ## Deduplication Uses the conversation ID as the dedup key. Default window: 1 minute. Each conversation can only trigger the workflow once within the window. ## Use case examples ### Follow-up based on voice disposition Different AI-classified dispositions get different follow-up strategies. 1. **Trigger:** Conversation ended → Agent Type: voice 2. **Action:** [Condition](/workflows/actions/condition) — Check `{{trigger.conversation.disposition}}` * **Path: contains `Interested`** * [Send email](/workflows/actions/send-email) — *"Hi `{{firstName}}`, great chatting! Here are the details we discussed..."* * [Update contact](/workflows/actions/update-contact) — Add "interested" tag * **Path: contains `Voicemail`** * [Wait](/workflows/actions/wait) — 4 hours * [Voice call](/workflows/actions/voice-call) — Retry * **Path: contains `No Answer`** * [Send SMS](/workflows/actions/send-sms) — *"Hi `{{firstName}}`, I just tried to reach you. Let me know a good time."* * **Default** * [Update contact](/workflows/actions/update-contact) — Add "needs-review" tag ### Post SMS summary to Slack Every finalized SMS conversation gets a summary posted to your team channel. 1. **Trigger:** Conversation ended → Agent Type: chat 2. **Action:** [Condition](/workflows/actions/condition) — `{{trigger.conversation.channel}}` equals `sms` 3. **Action:** [Slack message](/workflows/actions/slack-message) — #sms-conversations * Message: *"SMS with `{{firstName}}` `{{lastName}}` (`{{trigger.conversation.messageCount}}` messages, `{{trigger.conversation.disposition}}`)\n\n`{{trigger.conversation.summary}}`"* ### Post-call summary to Slack Every completed voice call gets a summary posted to your team channel. 1. **Trigger:** Conversation ended → Agent Type: voice, Call disposition: `contains` `completed` 2. **Action:** [Slack message](/workflows/actions/slack-message) — #call-updates * Message: *"Call with `{{firstName}}` `{{lastName}}` (`{{trigger.conversation.duration}}`s, `{{trigger.conversation.disposition}}`)\n\n`{{trigger.conversation.summary}}`"* ### Sync conversation data to CRM Push conversation details to your CRM after every conversation, regardless of channel. 1. **Trigger:** Conversation ended (no filters) 2. **Action:** [Send webhook](/workflows/actions/send-webhook) — POST to CRM ```json theme={null} { "contact": "{{firstName}} {{lastName}}", "phone": "{{phone}}", "channel": "{{trigger.conversation.channel}}", "agentType": "{{trigger.conversation.agentType}}", "disposition": "{{trigger.conversation.disposition}}", "summary": "{{trigger.conversation.summary}}", "agent": "{{trigger.conversation.agentName}}" } ``` ### DNC compliance Automatically handle "do not call" requests on voice calls. 1. **Trigger:** Conversation ended → Agent Type: voice, Call disposition: `contains` `do_not_call` 2. **Action:** [Update contact](/workflows/actions/update-contact) — Add "dnc" tag 3. **Action:** [Slack message](/workflows/actions/slack-message) — #compliance * Message: *"`{{firstName}}` `{{lastName}}` (`{{phone}}`) requested Do Not Call."* ### Tag engaged web visitors When a web agent session finishes with a positive disposition, mark the contact as engaged. 1. **Trigger:** Conversation ended → Agent Type: web 2. **Action:** [Condition](/workflows/actions/condition) — `{{trigger.conversation.disposition}}` contains `Interested` 3. **Action:** [Update contact](/workflows/actions/update-contact) — Add "engaged-web" tag # Workflow triggers Source: https://docs.nedzo.ai/workflows/triggers/overview Explore the events that start a Nedzo workflow, including webhooks, schedules, contact creation, tag changes, inbound messages, and completed calls. A trigger is the event that kicks off your workflow. Every workflow starts with exactly one trigger node. When the event occurs, Nedzo checks if any active, published workflows match, and runs them. ## Available triggers | Trigger | Fires when... | | ---------------------------------------------------------------- | ---------------------------------------------------------- | | [Contact Created](/workflows/triggers/contact-created) | A new contact is added to your workspace | | [Contact Tagged](/workflows/triggers/contact-tagged) | A tag is added to or removed from a contact | | [Contact Replied](/workflows/triggers/contact-replied) | A contact sends an inbound message | | [Conversation Ended](/workflows/triggers/conversation-completed) | A voice call, chat conversation, or web agent session ends | | [Webhook](/workflows/triggers/webhook) | An external system sends a POST request | | [Schedule](/workflows/triggers/schedule) | A set time arrives (one-time or recurring) | ## Filters Most triggers support filters that narrow down when the workflow should run. Filters use AND logic — all filters must match for the workflow to fire. If you don't add any filters, the trigger fires on every matching event. ## Deduplication By default, triggers include a **1-minute deduplication window**. If the same event fires multiple times for the same contact within that window, only the first one runs the workflow. | Setting | Default | Range | | ------- | -------- | ------------------- | | Enabled | Yes | On/Off | | Window | 1 minute | 1 minute – 24 hours | You can adjust the window or disable deduplication entirely in the trigger settings. # Schedule trigger Source: https://docs.nedzo.ai/workflows/triggers/schedule Run Nedzo workflows on a time-based schedule, either one-time or recurring. Target contact segments with hourly, daily, or weekly cadences. Fires on a time-based schedule. Use it to run workflows at a specific date and time, or on a recurring cadence like every hour, daily, or weekly. Schedule triggers run against your contacts, so you can target specific segments with filters. ## When it fires The trigger fires at the scheduled time. For one-time schedules, it fires once and stops. For recurring schedules, it fires on every interval until you deactivate the workflow or the end date is reached. When the trigger fires, it creates one workflow execution per contact that matches your contact filters. If no filters are set, it runs for all contacts in the workspace. ## Configuration ### Schedule mode Choose between two modes: | Mode | Description | | ------------- | -------------------------------------- | | **One-time** | Fires once at a specific date and time | | **Recurring** | Fires repeatedly on a set interval | ### One-time schedule | Field | Required | Description | | ----------- | -------- | ----------------------------------------------------------------------------------- | | Date & time | Yes | When the workflow should run. Must be at least 1 minute in the future. | | Timezone | Yes | IANA timezone for the scheduled time (e.g., `America/New_York`, `Europe/Amsterdam`) | ### Recurring schedule | Field | Required | Description | | ------------ | ----------- | -------------------------------------------------------------------------- | | Frequency | Yes | How often to run: `minutes`, `hours`, `days`, `weekly`, or `monthly` | | Interval | Depends | Number of units between runs. For `minutes`, minimum interval is 5. | | Cron pattern | Alternative | Advanced: set a custom cron expression instead of using frequency/interval | | Timezone | Yes | IANA timezone for the schedule | | End date | No | Optional date when the recurring schedule stops running | **Frequency examples:** | Frequency | Interval | Result | | --------- | -------- | ---------------- | | minutes | 30 | Every 30 minutes | | hours | 2 | Every 2 hours | | days | 1 | Once a day | | weekly | 1 | Once a week | | monthly | 1 | Once a month | For `minutes` frequency, the minimum interval is 5 minutes to prevent excessive workflow executions. ### Contact filters Narrow down which contacts the workflow runs for. Add filters to target specific segments instead of running for every contact in your workspace. Each filter has three parts: | Part | Description | | ------------ | ------------------------------------------------------------- | | **Field** | The contact field to check (e.g., email, phone, tag) | | **Operator** | How to compare (equals, not equals, contains, is empty, etc.) | | **Value** | The value to compare against | All filters use AND logic — every filter must match for the contact to be included. **No filters:** If you don't add any filters, the workflow runs for every contact in the workspace. ## Data available When this trigger fires, the following data is available as variables in your workflow: | Variable | Description | Example | | ------------------ | ---------------------------- | ------------------- | | `{{contactId}}` | The contact's unique ID | `a1b2c3d4-e5f6-...` | | `{{firstName}}` | First name | `John` | | `{{lastName}}` | Last name | `Doe` | | `{{phone}}` | Phone number in E.164 format | `+14155551234` | | `{{email}}` | Email address | `john@example.com` | | `{{businessName}}` | Company name | `Acme Inc` | ## Schedule status After publishing a workflow with a schedule trigger, you can see the schedule status on the workflow: | Field | Description | | ------------ | ----------------------------------------- | | **Next run** | When the workflow will fire next | | **Last run** | When the workflow last fired | | **Active** | Whether the schedule is currently running | Deactivating the workflow pauses the schedule. Reactivating it resumes from the next scheduled time. ## Use case examples ### One-time campaign blast Send an SMS to all contacts tagged "promo-list" at a specific date and time. 1. **Trigger:** Schedule (One-time) — January 15, 2026 at 10:00 AM EST * Filter: Has tag → equals → "promo-list" 2. **Action:** [Send SMS](/workflows/actions/send-sms) * Message: *"Hi `{{firstName}}`, we have a special offer for you! Reply YES to learn more."* ### Daily follow-up calls Call contacts who haven't been reached yet, every day at 9 AM. 1. **Trigger:** Schedule (Recurring) — Daily at 9:00 AM * Filter: Has tag → equals → "needs-call" * Filter: Has tag → not equals → "called" 2. **Action:** [Voice call](/workflows/actions/voice-call) — Sales agent 3. **Action:** [Update contact](/workflows/actions/update-contact) — Add "called" tag ### Weekly check-in SMS Send a weekly check-in message to active customers. 1. **Trigger:** Schedule (Recurring) — Weekly, Monday at 10:00 AM * Filter: Has tag → equals → "active-customer" 2. **Action:** [Send SMS](/workflows/actions/send-sms) * Message: *"Hi `{{firstName}}`, hope your week is off to a great start! Let us know if you need anything."* ### Monthly renewal reminder Email customers 30 days before their subscription renews. 1. **Trigger:** Schedule (Recurring) — Monthly * Filter: Has tag → equals → "renewal-due" 2. **Action:** [Send email](/workflows/actions/send-email) * Subject: *"Your subscription renewal is coming up"* * Body: *"Hi `{{firstName}}`, your subscription renews next month. Let us know if you have any questions."* 3. **Action:** [Update contact](/workflows/actions/update-contact) — Remove "renewal-due" tag # Webhook trigger Source: https://docs.nedzo.ai/workflows/triggers/webhook Start a Nedzo workflow from any external system using an HTTP POST request. Connect tools like Zapier, Make, n8n, or your own backend to trigger automations. Fires when an external system sends an HTTP POST request to the workflow's unique webhook URL. Use this to connect Nedzo to any external tool — Zapier, Make, n8n, your own backend, or anything that can send HTTP requests. ## When it fires The trigger fires immediately when a valid POST request hits the webhook URL. Each request creates one workflow execution. Unlike other triggers, webhook triggers are not deduplicated — every request runs the workflow. ## Configuration When you add a webhook trigger, Nedzo generates two things: | Field | Description | | ----------------- | -------------------------------------------------------------------------------------------------------- | | **Webhook URL** | The unique URL to send requests to. This stays the same even if you regenerate the token or change auth. | | **Webhook token** | A 64-character secret embedded in the URL path. Cryptographically identifies the caller. | ### Regenerating the token Click **Regenerate** in the trigger settings to create a new token. The old token stops working immediately. The URL stays the same. ### Authorization The 64-character token in the URL authenticates the caller by itself. For extra protection, configure an additional authorization scheme from the trigger settings. | Scheme | How it works | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **None** (default) | Token in the URL is the only credential. Fine for most integrations — the token is 256 bits of entropy. | | **HMAC** | Caller signs each request with a shared secret using HMAC-SHA256. Timestamp-bound so replayed requests are rejected. Recommended if you need tamper-proof payloads. | | **Bearer** | Caller sends a static token in the `Authorization: Bearer ` header. | | **Basic** | Caller sends `Authorization: Basic ` using HTTP Basic auth. | Failed auth attempts are rate-limited per trigger. After too many bad attempts in a short window the trigger returns `401` until the cooldown ends. #### HMAC details When HMAC is selected, Nedzo gives you a shared secret and two header names (defaults: `X-Nedzo-Signature`, `X-Nedzo-Timestamp`). To sign a request: 1. Take the current Unix timestamp in seconds (e.g. `1730000000`). 2. Build the signed string: `{timestamp}.{rawRequestBody}`. 3. Compute `HMAC-SHA256(secret, signedString)` and hex-encode the result (64 characters). 4. Send the hex digest in the signature header and the timestamp in the timestamp header. Example (Node.js): ```javascript theme={null} import crypto from 'crypto'; const timestamp = Math.floor(Date.now() / 1000); const body = JSON.stringify({ firstName: 'John', phone: '+14155551234' }); const signature = crypto .createHmac('sha256', SHARED_SECRET) .update(`${timestamp}.${body}`) .digest('hex'); await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Nedzo-Timestamp': String(timestamp), 'X-Nedzo-Signature': signature, }, body, }); ``` Requests with a timestamp more than 5 minutes off from the server clock are rejected. Each signature can only be used once inside the tolerance window (replay protection). ### Sample payload The trigger UI lets you capture a **sample payload**. Send a test request to the webhook URL, and Nedzo captures the payload structure. This is used to: * Show available fields in the condition builder * Provide autocomplete for variable references in actions * Auto-detect contact fields (email, phone) for downstream contact-scoped actions ### Contact field mapping If your workflow has a [Make phone call](/workflows/actions/voice-call), [Send SMS](/workflows/actions/send-sms), [Send email](/workflows/actions/send-email), or [Update contact](/workflows/actions/update-contact) action set to "Contact", the webhook payload itself supplies the contact data — there's no contact in scope at trigger time. The trigger config shows a **Contact fields** section with one row per required field, based on which actions you've added downstream. The required fields depend on the action: * **Send SMS / Make phone call** require **Phone**. * **Send email** requires **Email**. * **Update contact** (create or update) requires an **identifier** — either **Phone** or **Email**. Update contact resolves the contact by phone first, then email, so mapping either one is enough. The workflow no longer silently creates a blank contact when neither is present. For each field, Nedzo: 1. **Auto-detects** common field names from the captured sample payload — `email` / `emailAddress` / `email_address` for Email, and `phone` / `phoneNumber` / `phone_number` for Phone. Auto-detection is case-insensitive and works on top-level fields. 2. **Falls back to a dropdown** of every string-valued path in the sample payload, so you can map a non-standard field. Useful for nested values like `data.lead.cell` or aliases like `mobile`. If a required field can't be resolved (no auto-detect match, no explicit mapping, no `contactId` in the payload), the **Publish** button is disabled with a tooltip explaining what's missing, and the webhook trigger node shows an attention indicator on the canvas. Once every required field is resolvable, Publish becomes available. #### Bypass mapping with `contactId` If your payload includes a `contactId` that matches an existing contact, that takes precedence — the Email and Phone mapping is bypassed entirely and the contact is loaded from the database. Useful when the upstream system already knows the Nedzo contact ID. ```json theme={null} { "contactId": "a1b2c3d4-..." } ``` ## Sending a request Send an HTTP POST with a JSON body. The 64-character token is part of the URL path: ```bash theme={null} curl -X POST "https://api.nedzo.ai/webhooks/trigger/{webhook-token}" \ -H "Content-Type: application/json" \ -d '{ "firstName": "John", "lastName": "Doe", "phone": "+14155551234", "email": "john@example.com", "dealValue": 5000, "source": "website-form" }' ``` If the trigger is configured with HMAC, Bearer, or Basic auth, add the required headers as well — see the [Authorization](#authorization) section above. ### Request requirements | Requirement | Details | | ------------------ | ---------------------------------------------------------- | | Method | POST only | | Content-Type | `application/json` | | Body | Valid JSON, max 1 MB | | Extra auth headers | Only required if the trigger has HMAC/Bearer/Basic enabled | ## Data available Every field you send in the request body is available as a variable using the `{{trigger.webhook.body.*}}` pattern. For the example payload above: | Variable | Value | | ------------------------------------ | ------------------ | | `{{trigger.webhook.body.firstName}}` | `John` | | `{{trigger.webhook.body.lastName}}` | `Doe` | | `{{trigger.webhook.body.phone}}` | `+14155551234` | | `{{trigger.webhook.body.email}}` | `john@example.com` | | `{{trigger.webhook.body.dealValue}}` | `5000` | | `{{trigger.webhook.body.source}}` | `website-form` | If you include a `contactId` in the payload and it matches an existing contact, the contact's standard fields (`{{firstName}}`, `{{phone}}`, etc.) are also loaded. Otherwise, contact-scoped actions resolve the contact from the email/phone fields you mapped in the trigger config — see [Contact field mapping](#contact-field-mapping) above. ## Use case examples ### Trigger from Zapier on form submission Connect a Zapier Zap to send form data to Nedzo and call the lead. 1. **In Zapier:** Create a Zap with your form tool as trigger, and a Webhook action pointing to your Nedzo webhook URL 2. **Map fields:** firstName, lastName, phone, email 3. **In Nedzo:** * **Trigger:** Webhook * **Action:** [Update contact](/workflows/actions/update-contact) — Create contact from webhook data * First name: `{{trigger.webhook.body.firstName}}` * Phone: `{{trigger.webhook.body.phone}}` * Tags: "website-lead" * **Action:** [Wait](/workflows/actions/wait) — 2 minutes * **Action:** [Voice call](/workflows/actions/voice-call) — Sales agent ### Trigger from your backend on deal close Your app sends a POST when a deal closes. 1. **Trigger:** Webhook 2. **Action:** [Send SMS](/workflows/actions/send-sms) * Message: *"Congrats `{{trigger.webhook.body.firstName}}`, your deal is confirmed! We'll be in touch with next steps."* 3. **Action:** [Slack message](/workflows/actions/slack-message) — #wins * Message: *"Deal closed: `{{trigger.webhook.body.firstName}}` `{{trigger.webhook.body.lastName}}` — \$`{{trigger.webhook.body.dealValue}}`"* ### Trigger from Make (Integromat) Use a Make HTTP module to call the webhook URL after any automation step. 1. **In Make:** Add an HTTP "Make a request" module 2. **URL:** Your Nedzo webhook URL 3. **Method:** POST 4. **Body:** JSON with your data 5. **In Nedzo:** Build any workflow using the incoming data ### Manual trigger via API Use the webhook URL from your own code or Postman for testing or custom integrations. ```javascript theme={null} const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contactId: 'existing-contact-id', customData: 'anything you need' }) }); ```