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

# Trigger a Flow

> Trigger a flow from your own backend, CRM, or any tool not in our native trigger list

## Overview

Any flow built with a **Webhook (Generic)** trigger (see [Triggers](/flows/triggers#triggers-in-detail)) can be started by sending it a POST request. When TextYess receives a valid request, it resolves or creates the contact, maps your JSON body into the flow's starting memory, and starts the flow.

<Note>
  Publishing the flow generates its webhook URL. Open the trigger node on the canvas afterwards to copy the URL, the credential to authenticate with, and a ready-to-run cURL command for that flow.
</Note>

### Path Parameters

<ParamField path="id" type="string" required>
  The id of the flow you want to trigger. Copy it from the flow's Webhook trigger node after publishing.
</ParamField>

## Authentication

Most flows require a credential — either header works on every flow in your organization:

```bash theme={null}
x-flow-token: your_flow_token
```

```bash theme={null}
x-webhook-secret: your_webhook_secret
```

<ParamField header="x-flow-token" type="string">
  Your organization's flow-trigger token. One token authenticates every webhook-triggered flow, so it's the easiest option when you're wiring up several flows to the same external system (e.g. one HubSpot workflow calling multiple flows). Find it, and rotate it, in the [Developers section](https://ai.textyess.com/developers).
</ParamField>

<ParamField header="x-webhook-secret" type="string">
  A secret generated for this specific flow only. Copy it from the flow's Webhook trigger node after publishing.
</ParamField>

<Note>
  Flows created before webhook secrets were introduced keep working without a credential, for backwards compatibility. All other flows require either header — a request with neither (or an invalid value) is rejected.
</Note>

<RequestExample>
  ```bash Flow token (recommended) theme={null}
  curl --request POST \
    --url https://api-ai.textyess.com/webhooks/flows/68dd9a79dd6e8f8774733bd7 \
    --header 'Content-Type: application/json' \
    --header 'x-flow-token: your_flow_token' \
    --data '{
      "phone_number": "+393475156348",
      "country_code": "IT",
      "first_name": "John"
    }'
  ```

  ```bash Per-flow secret theme={null}
  curl --request POST \
    --url https://api-ai.textyess.com/webhooks/flows/68dd9a79dd6e8f8774733bd7 \
    --header 'Content-Type: application/json' \
    --header 'x-webhook-secret: your_webhook_secret' \
    --data '{
      "phone_number": "+393475156348",
      "country_code": "IT",
      "first_name": "John"
    }'
  ```
</RequestExample>

### Body Parameters

A webhook must identify a person: include **`email` or `phone_number`** (at least one).

<ParamField body="email" type="string" placeholder="john@example.com">
  The recipient's email address. Required when `phone_number` is not provided. When present, the contact is resolved or created by email.
</ParamField>

<ParamField body="phone_number" type="string" placeholder="+393475156348">
  The recipient's phone number (E.164, e.g. "+393475156348"). Required when `email` is not provided. Used for WhatsApp flows.
</ParamField>

<ParamField body="country_code" type="string" placeholder="IT">
  The two-letter country code. **Required when `phone_number` is set** (it must match the WhatsApp template language). Not needed for email-only payloads.
</ParamField>

<ParamField body="first_name" type="string" placeholder="John">
  The recipient's first name. Optional.
</ParamField>

<ParamField body="last_name" type="string" placeholder="Doe">
  The recipient's last name. Optional.
</ParamField>

<ParamField body="email_opt_in" type="boolean" placeholder="true">
  Optional. Declares marketing-email consent for the resolved contact. When omitted, consent is not touched (we never assume it). Set to `true` for newsletter/subscribe webhooks.
</ParamField>

<ParamField body="custom_url" type="string" placeholder="https://example.com/user/john">
  Optional URL for custom redirects. For abandoned-cart automations, this should be the cart recovery URL.
</ParamField>

<ParamField body="discount_code" type="string" placeholder="DISCOUNT10">
  Optional discount code you want to send to the user, so you can inject a unique code per user directly into the WhatsApp template.
</ParamField>

<Tip>
  Any other field you send is captured too and available to the flow as `payload.<field>` — for example a CRM tag, an order id, or a custom score. See [Webhook (Generic)](/flows/triggers#triggers-in-detail) for how the payload maps into flow memory.
</Tip>

### Response

<ResponseField name="success" type="boolean">
  Whether the webhook was accepted and the flow was started.
</ResponseField>

<ResponseField name="workflowId" type="string">
  The id of the started flow run. Only present when the webhook triggered a flow.
</ResponseField>

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "workflowId": "wf_12345678"
  }
  ```

  ```json 400 theme={null}
  {
    "message": "Webhook payload missing required fields",
    "errors": ["phone_number requires country_code"],
    "hint": "Webhook payload must include email or phone_number (country_code is required when phone_number is set)"
  }
  ```

  ```json 403 theme={null}
  {
    "message": "Invalid webhook secret"
  }
  ```
</ResponseExample>

## Important Notes

1. **Template dedup:** The same phone number cannot receive the identical template message within a 72-hour window — this applies to any trigger, not just webhooks.
2. **Abandoned Cart Automation:** When building abandoned-cart flows, `custom_url` in the payload should be the cart recovery URL, and in a "Send template message" step of type "Abandoned checkout" only the call-to-action text can be customized (not the link).
3. **Language Requirements:** The template language must match the country code in the payload — e.g. an Italian (IT) template requires `country_code: "IT"`; mismatched languages prevent delivery.
