Test your journeys with the Simulation API

Santiago Cardona Updated by Santiago Cardona

The Simulation API lets you run your journeys programmatically — without sending real WhatsApp messages. Think of it as having a conversation with your journey through code.

This is especially useful if you’re building AI agents, automated testing pipelines, or any external system that needs to interact with your journey logic.

This API is under active development. Breaking changes are expected before August 2026, so backward compatibility is not guaranteed until then.

Before you start

You’ll need two things:

  1. A Turn.io API token. You can generate one from the Turn.io dashboard under Settings → API & Webhooks. This token authenticates all your API requests.
  2. A published journey. The journey you want to simulate must have a published production (or staging) revision. You’ll use the journey’s UUID, which you can find in the URL when viewing the journey in the canvas.

How it works

The Simulation API uses a single endpoint for everything:

POST https://whatsapp.turn.io/v1/journeys/<journey-uuid>/simulation

This one endpoint handles both starting a simulation and sending replies to it. The API knows what to do based on whether a session already exists for your simulation_id:

  • First request with a new simulation_id: Creates a new simulation and starts the journey.
  • Subsequent requests with the same simulation_id: Sends input to the running session.

The simulation_id is an identifier you create. It can be any string between 6 and 32 characters — for example, test-run-001 or agent-session-42. The API uses the combination of your journey UUID and simulation_id to track each session.

Starting a simulation

To kick off a simulation, send a POST request with a simulation_id:

curl -X POST "https://whatsapp.turn.io/v1/journeys/<journey-uuid>/simulation" \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "my-session-001"
}'

The API responds with the journey’s first output:

{
"state": "waiting_for_input",
"message": "Welcome! What is your name?",
"context": {
"contact": {
"name": "John",
"language": "eng"
}
}
}
Optional parameters

You can customize the simulation when starting it:

Parameter

Default

Description

revision

"production"

Which version of the journey to simulate. Use "staging" to test unpublished changes.

contact

{}

Contact attributes to use during the simulation. Overrides the defaults (e.g., name, language).

input

An initial message to send when the journey starts, if the first card expects input.

For example, to simulate with a specific contact name and language:

curl -X POST "https://whatsapp.turn.io/v1/journeys/<journey-uuid>/simulation" \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{
"revision": "production",
"contact": {
"name": "Maria",
"language": "por"
}
}'

Sending replies

When the journey is waiting for input (the response state is "waiting_for_input"), send the user’s reply with the same simulation_id:

curl -X POST "https://whatsapp.turn.io/v1/journeys/<journey-uuid>/simulation" \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "my-session-001",
"input": "Maria"
}'

Response:

{
"state": "end",
"message": "Thanks Maria! Your registration is complete."
}

When state is "end", the journey has finished. There’s no more input to send.

A full conversation example

Let’s walk through a complete multi-turn simulation. Imagine a journey that helps users book appointments.

Step 1 — Start the journey:

curl -X POST "https://whatsapp.turn.io/v1/journeys/d144cb30-f759-4260-82ba-07353f0e389f/simulation" \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "booking-test-01",
"contact": { "name": "Maria" }
}'
{
"state": "waiting_for_input",
"message": "Hi Maria! How can we help you today?\n\nExclusively reply with one of the following options:\nBook appointment, Check status, Speak to agent",
"context": {
"contact": { "name": "Maria", "language": "eng" }
}
}

Notice how button options appear as text in the message. To select one, reply with the exact option text.

Step 2 — Choose an option:

curl -X POST "https://whatsapp.turn.io/v1/journeys/d144cb30-f759-4260-82ba-07353f0e389f/simulation" \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "booking-test-01",
"input": "Book appointment"
}'
{
"state": "waiting_for_input",
"message": "Great! What date works for you?"
}

Step 3 — Complete the journey:

curl -X POST "https://whatsapp.turn.io/v1/journeys/d144cb30-f759-4260-82ba-07353f0e389f/simulation" \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "booking-test-01",
"input": "Next Monday"
}'
{
"state": "end",
"message": "Your appointment is booked for next Monday. See you then!"
}

Understanding the response

Every response from the Simulation API includes these fields:

Field

Description

state

Either "waiting_for_input" (the journey expects a reply) or "end" (the journey has finished).

message

The text output from the journey. This is what would normally be sent as a WhatsApp message.

context

The current variable context, including contact fields and any variables set by the journey. Only present when starting a simulation.

How journey content appears in responses

Since simulations don’t send real WhatsApp messages, certain content types are represented as text:

  • Buttons and lists: Options are appended to the message, for example: "Exclusively reply with one of the following options: Option1, Option2". Reply with the exact option text.
  • Media (images, videos, documents, audio): Returned as descriptive text with a temporary URL, for example: "Image available at: https://...".

Things to keep in mind

  • Sessions expire after inactivity. If you don’t send a request for a few minutes, the session will time out. Start a new one with a fresh simulation_id if that happens.
  • No real messages are sent. The journey runs entirely in-memory. No WhatsApp messages, no contact records, no side effects.
  • AI agents work too. If your journey includes AI agent blocks with memory enabled, the AI will remember earlier messages in the same simulation session — just like it would in a real conversation.
  • Use unique simulation_id values for parallel sessions. Each unique simulation_id creates an independent session, so you can run multiple simulations of the same journey at the same time.
  • Test staging changes before publishing. Set revision to "staging" to simulate the latest draft of your journey without publishing it first.

Error reference

If something goes wrong, the API returns a JSON object with an error field:

Status

Error

What it means

400

simulation_id is required

You didn’t include a simulation_id in the request body.

400

simulation_id must be between 6 and 32 characters

Your simulation_id is too short or too long.

400

Invalid Journey UUID

The journey UUID in the URL isn’t a valid UUID format.

400

Journey has no published production revision

The journey hasn’t been published yet. Publish it first or use "staging".

400

Journey has no staging revision

There’s no staging revision available for this journey.

400

Journey is disabled

The journey is currently disabled in Turn.io.

400

Journey has been deleted

The journey has been deleted.

400

Invalid revision, must be 'production' or 'staging'

The revision value isn’t valid. Use "production" or "staging".

400

Invalid input, must be a non-empty string

The input field is empty or missing when replying to a session.

404

Journey not found

No journey exists with that UUID for your number.

404

Simulation not found or expired

The session has expired or doesn’t exist. Start a new one.

Was this article helpful?

How to migrate journeys between accounts

Contact