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

# Trigger Customer Journeys Programmatically via API

> Enroll customers or entire segments into Tappd journeys from any backend service, webhook, or automation pipeline using a single authenticated API call.

Journey API Triggers let you start a customer journey from anywhere outside the Tappd dashboard — your backend server, a payment webhook, a cron job, or any third-party integration. Instead of waiting for an in-app event, you send a single `POST` request with a customer identifier and an optional payload, and Tappd queues the enrollment immediately. All of the journey's steps — messages, delays, conditions, and webhooks — then execute according to the journey's configured rules.

## Prerequisites

Before you make your first API trigger call, make sure you have:

1. **An API key** — Generate one in **Settings → API Keys** inside the Tappd dashboard.
2. **An active journey** — The journey must be published and configured with **API** as its trigger type.
3. **An existing customer** — The customer must already exist in your workspace. Use the SDK's `identify()` method or the Customers API to create them first.

## Endpoint

```text theme={null}
POST https://sdk.gotappd.com/api/v1/journeys/:workspaceId/:journeyId/trigger

Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```

Replace `:workspaceId` and `:journeyId` with the IDs found in your Tappd dashboard.

## Request Parameters

### Single Customer

Use these fields to trigger a journey for one customer at a time.

<ParamField body="external_id" type="string">
  Your internal user ID (also accepted as `externalId`). This is the most common identifier to use — it matches the value you pass to `identify()` in the SDK.
</ParamField>

<ParamField body="email" type="string">
  The customer's email address. Use this as an alternative to `external_id` when you only have the email available.
</ParamField>

<ParamField body="customerId" type="string">
  The Tappd customer ID. Use this when you already have the Tappd customer record and want the most direct lookup.
</ParamField>

<ParamField body="payload" type="object">
  A custom JSON object sent alongside the trigger (also accepted as `properties`). Values are available in Liquid templates throughout the journey using `{{ api.properties.* }}` or `{{ api.payload.* }}`.
</ParamField>

### Bulk Operations

Use these fields to enroll multiple customers in a single request.

<ParamField body="external_ids" type="string[]">
  An array of your internal user IDs (also accepted as `externalIds`). Maximum 1,000 entries. Customers whose IDs are not found are silently skipped — no error is returned for individual missing IDs.
</ParamField>

<ParamField body="segment_ids" type="string[]">
  An array of Tappd segment IDs (also accepted as `segmentIds`). Enrolls every customer who belongs to those segments. Maximum 10 segments per request. An invalid segment ID returns an error for the entire request.
</ParamField>

<ParamField body="exclude_external_ids" type="string[]">
  An array of external IDs to exclude from enrollment (also accepted as `excludeExternalIds`). Applied **after** any intersection logic. Maximum 1,000 entries.
</ParamField>

<ParamField body="payload" type="object">
  A custom JSON object shared with all enrolled customers (also accepted as `properties`). Available in Liquid templates as `{{ api.properties.* }}`.
</ParamField>

<Note>
  If you supply both `external_ids` and `segment_ids`, only customers who appear in **both** sets are enrolled (intersection). Use `exclude_external_ids` to remove specific customers from the final list regardless of how they were matched.
</Note>

## Code Examples

<Tabs>
  <Tab title="cURL">
    <CodeGroup>
      ```bash Single — External ID theme={null}
      curl -X POST \
        'https://sdk.gotappd.com/api/v1/journeys/WORKSPACE_ID/JOURNEY_ID/trigger' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "external_id": "user_12345",
          "payload": {
            "orderId": "ORD-12345",
            "orderTotal": 99.99,
            "currency": "USD"
          }
        }'
      ```

      ```bash Single — Email theme={null}
      curl -X POST \
        'https://sdk.gotappd.com/api/v1/journeys/WORKSPACE_ID/JOURNEY_ID/trigger' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "email": "customer@example.com",
          "payload": {
            "eventType": "purchase_completed",
            "amount": 199.99
          }
        }'
      ```

      ```bash Single — Customer ID theme={null}
      curl -X POST \
        'https://sdk.gotappd.com/api/v1/journeys/WORKSPACE_ID/JOURNEY_ID/trigger' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "customerId": "507f1f77bcf86cd799439011",
          "payload": {
            "subscriptionTier": "premium",
            "upgradeDate": "2024-01-15"
          }
        }'
      ```

      ```bash Bulk — External IDs theme={null}
      curl -X POST \
        'https://sdk.gotappd.com/api/v1/journeys/WORKSPACE_ID/JOURNEY_ID/trigger' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "external_ids": ["user_123", "user_456", "user_789"],
          "payload": {
            "campaign": "summer_sale",
            "discount": 20
          }
        }'
      ```

      ```bash Bulk — Segment IDs theme={null}
      curl -X POST \
        'https://sdk.gotappd.com/api/v1/journeys/WORKSPACE_ID/JOURNEY_ID/trigger' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "segment_ids": ["507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012"],
          "payload": {
            "event": "announcement",
            "message": "New feature available"
          }
        }'
      ```

      ```bash Bulk — Intersection theme={null}
      curl -X POST \
        'https://sdk.gotappd.com/api/v1/journeys/WORKSPACE_ID/JOURNEY_ID/trigger' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "external_ids": ["user_123", "user_456"],
          "segment_ids": ["507f1f77bcf86cd799439011"],
          "payload": {
            "campaign": "vip_promo"
          }
        }'
      ```

      ```bash Bulk — With Exclusion theme={null}
      curl -X POST \
        'https://sdk.gotappd.com/api/v1/journeys/WORKSPACE_ID/JOURNEY_ID/trigger' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "segment_ids": ["507f1f77bcf86cd799439011"],
          "exclude_external_ids": ["user_123", "user_456"],
          "payload": {
            "campaign": "general_announcement"
          }
        }'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="JavaScript / Node.js">
    ```javascript theme={null}
    async function triggerJourney(workspaceId, journeyId, identifier, payload) {
      const response = await fetch(
        `https://sdk.gotappd.com/api/v1/journeys/${workspaceId}/${journeyId}/trigger`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer YOUR_API_KEY`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            external_id: identifier,
            payload,
          }),
        }
      );

      return response.json();
    }

    // Single customer — external ID
    await triggerJourney("workspace_123", "journey_456", "user_789", {
      orderId: "ORD-12345",
      orderTotal: 99.99,
    });

    // Bulk — segment with exclusions
    await fetch(
      `https://sdk.gotappd.com/api/v1/journeys/workspace_123/journey_456/trigger`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer YOUR_API_KEY`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          segment_ids: ["507f1f77bcf86cd799439011"],
          exclude_external_ids: ["user_123"],
          payload: { campaign: "re-engagement" },
        }),
      }
    );
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import requests

    def trigger_journey(workspace_id, journey_id, external_id, payload):
        url = f"https://sdk.gotappd.com/api/v1/journeys/{workspace_id}/{journey_id}/trigger"
        headers = {
            "Authorization": "Bearer YOUR_API_KEY",
            "Content-Type": "application/json",
        }
        data = {
            "external_id": external_id,
            "payload": payload,
        }
        response = requests.post(url, json=data, headers=headers)
        response.raise_for_status()
        return response.json()

    # Single customer
    result = trigger_journey(
        "workspace_123",
        "journey_456",
        "user_789",
        {"orderId": "ORD-12345", "orderTotal": 99.99},
    )

    # Bulk — multiple external IDs
    bulk_response = requests.post(
        "https://sdk.gotappd.com/api/v1/journeys/workspace_123/journey_456/trigger",
        headers={
            "Authorization": "Bearer YOUR_API_KEY",
            "Content-Type": "application/json",
        },
        json={
            "external_ids": ["user_123", "user_456", "user_789"],
            "payload": {"campaign": "summer_sale", "discount": 20},
        },
    )
    ```
  </Tab>
</Tabs>

## Response Format

**Single customer success:**

```json theme={null}
{
  "success": true,
  "message": "Journey triggered successfully",
  "data": {
    "customerId": "507f1f77bcf86cd799439011",
    "journeyId": "507f1f77bcf86cd799439012",
    "status": "queued",
    "queued": true,
    "jobId": "enroll-507f1f77bcf86cd799439012-507f1f77bcf86cd799439011-1704067200000",
    "note": "Enrollment is queued and will be confirmed after entry rules validation"
  }
}
```

**Bulk operation success:**

```json theme={null}
{
  "success": true,
  "message": "Journey triggered successfully",
  "data": {
    "journeyId": "507f1f77bcf86cd799439012",
    "status": "queued",
    "queued": true,
    "customersEnrolled": 3,
    "customerIds": [
      "507f1f77bcf86cd799439011",
      "507f1f77bcf86cd799439012",
      "507f1f77bcf86cd799439013"
    ],
    "jobIds": [
      "enroll-507f1f77bcf86cd799439012-507f1f77bcf86cd799439011-1704067200000",
      "enroll-507f1f77bcf86cd799439012-507f1f77bcf86cd799439012-1704067200001",
      "enroll-507f1f77bcf86cd799439012-507f1f77bcf86cd799439013-1704067200002"
    ],
    "note": "Enrollments are queued and will be confirmed after entry rules validation"
  }
}
```

## Error Responses

| Error                        | HTTP Status | Cause                                                                    |
| ---------------------------- | ----------- | ------------------------------------------------------------------------ |
| `Customer not found`         | 404         | No customer matched the supplied `external_id`, `email`, or `customerId` |
| `Journey not found`          | 404         | The journey ID doesn't exist or the journey is not active                |
| `Invalid journey type`       | 400         | The journey's trigger type is not set to **API**                         |
| `Missing required parameter` | 400         | No customer identifier was supplied in the request body                  |
| `Array size exceeded`        | 400         | `external_ids` contains more than 1,000 entries                          |
| `Error resolving segments`   | 400         | One or more `segment_ids` don't exist in the workspace                   |

<Note>
  Missing entries in the `external_ids` array are silently skipped. Invalid `segment_ids`, however, fail the entire request — verify your segment IDs before sending bulk calls.
</Note>

## Using Payload Data in Journeys

The `payload` (or `properties`) object you include in the trigger request is available in every Liquid template inside the journey.

Access payload values using either syntax:

```liquid theme={null}
{{ api.properties.orderId }}
{{ api.payload.orderId }}
```

**Example — trigger payload:**

```json theme={null}
{
  "external_id": "user_123",
  "payload": {
    "orderId": "ORD-12345",
    "orderTotal": 99.99,
    "productName": "Premium Plan"
  }
}
```

**Example — Liquid template in an in-app message:**

```liquid theme={null}
Thank you for your purchase!

Order ID: {{ api.properties.orderId }}
Total: ${{ api.properties.orderTotal }}
Product: {{ api.properties.productName }}
```

**Rendered output:**

```text theme={null}
Thank you for your purchase!

Order ID: ORD-12345
Total: $99.99
Product: Premium Plan
```

You can also forward payload data through journey webhook steps:

```json theme={null}
{
  "url": "https://example.com/webhook",
  "method": "POST",
  "body": {
    "orderId": "{{ api.properties.orderId }}",
    "customerEmail": "{{ customer.email }}",
    "orderTotal": "{{ api.properties.orderTotal }}"
  }
}
```

## Journey Configuration

<Steps>
  <Step title="Create a Journey">
    Open the Tappd dashboard and create a new journey, or open an existing draft journey.
  </Step>

  <Step title="Set Trigger Type to API">
    In the journey's settings panel, set the **Trigger Type** to **API**. Only journeys with this trigger type can be started via the API endpoint.
  </Step>

  <Step title="Configure Entry Rules">
    Optionally, set entry rules to control how customers enter the journey:

    * **Maximum entries per customer** — how many times a single customer can enroll
    * **Re-enrollment** — whether customers can re-enter after completing the journey
    * **Entry conditions** — additional attribute or event conditions that must be met before enrollment is confirmed
  </Step>

  <Step title="Add Steps">
    Build out the journey with messages, delays, conditions, webhooks, and any other steps your workflow requires.
  </Step>

  <Step title="Publish the Journey">
    Publish the journey to make it live. The API endpoint will return an error for unpublished or inactive journeys.
  </Step>
</Steps>

## Re-Enrollment Behavior

Understanding how re-enrollment works helps you avoid unexpected behavior when triggering the same journey for a customer more than once.

**Multiple entries allowed** (`maxEntriesPerCustomer` > 1): If the customer has fewer completed entries than the maximum, any active enrollment is exited and a new one is created immediately.

**Single entry only** (`maxEntriesPerCustomer` is 1 or not set): If the customer has an active or completed enrollment, the API returns an "already enrolled" status. Enable re-enrollment in the journey settings to allow customers to re-enter after they complete the journey.

## Rate Limiting

The Journey Trigger API enforces a limit of **100 requests per minute per API key**. Requests that exceed this limit receive a `429 Too Many Requests` response. Check the rate limit headers in the response to track your current usage and implement exponential backoff in high-volume scenarios.

## Use Cases

<CardGroup cols={2}>
  <Card title="E-Commerce Order Confirmation" icon="bag-shopping">
    Trigger an order confirmation journey immediately after checkout completes. Pass order details — ID, total, items, shipping address — in the payload and use them to personalize every step.

    ```javascript theme={null}
    await triggerJourney(workspaceId, journeyId, order.customerId, {
      orderId: order.id,
      orderTotal: order.total,
      items: order.items,
      shippingAddress: order.shippingAddress,
    });
    ```
  </Card>

  <Card title="Subscription Management" icon="repeat">
    Fire a journey when a customer upgrades, downgrades, or cancels their subscription. Use the payload to tailor messaging based on their previous and new plan tiers.

    ```javascript theme={null}
    await triggerJourney(workspaceId, journeyId, user.externalId, {
      subscriptionTier: "premium",
      upgradeDate: new Date().toISOString(),
      previousTier: "basic",
    });
    ```
  </Card>

  <Card title="Event Registration" icon="calendar-check">
    Enroll registrants into a confirmation and reminder journey the moment they sign up for an event. Include event details in the payload so the messages feel personal and timely.

    ```javascript theme={null}
    await triggerJourney(workspaceId, journeyId, user.email, {
      eventId: event.id,
      eventName: event.name,
      eventDate: event.date,
      ticketType: "vip",
    });
    ```
  </Card>

  <Card title="Webhook Integration (Stripe, etc.)" icon="webhook">
    Receive a webhook from a payment processor or any third-party service and immediately enroll the customer in the appropriate journey — no manual intervention needed.

    ```javascript theme={null}
    app.post("/webhook/stripe", async (req, res) => {
      const { customer, amount } = req.body;
      await triggerJourney(workspaceId, journeyId, customer.id, {
        paymentAmount: amount,
        paymentMethod: "stripe",
        timestamp: new Date().toISOString(),
      });
      res.json({ success: true });
    });
    ```
  </Card>
</CardGroup>
