API de Simulação de Jornadas
A API de Simulação de Jornadas permite que sistemas externos (como agentes de IA) interajam com jornadas de forma conversacional sem enviar mensagens reais pelo WhatsApp. Isso é útil para testes, pré-visualizações e integração da lógica de jornadas em fluxos de trabalho externos.
Esta API está em desenvolvimento ativo e mudanças incompatíveis são esperadas nos próximos meses. A compatibilidade com versões anteriores não é garantida até pelo menos agosto de 2026.
A autenticação ocorre como em outras chamadas de API, com um token da Turn.io especificado no header HTTP Authorization, prefixado por Bearer e seguido de um token emitido pela interface do usuário da Turn.io.
Como funciona
O endpoint de simulação utiliza uma única rota POST que gerencia tanto a criação de novas simulações quanto o envio de entradas para simulações existentes:
- Se não existir uma simulação para o
simulation_idfornecido: uma nova sessão de simulação é criada e a jornada começa a ser executada. - Se já existir uma simulação para o
simulation_idfornecido: a entrada (input) fornecida é enviada para a sessão existente.
O simulation_id é sempre obrigatório e fornecido pelo chamador. Pode ser qualquer identificador de string entre 6 e 64 caracteres, então um UUID funciona. O endpoint usa a combinação do número ao qual o seu token pertence, do UUID da jornada (da URL) e do simulation_id (do corpo da requisição) para identificar sessões de simulação, então outro número usando o mesmo simulation_id nunca chega à sua sessão.
Iniciando uma simulação
Para iniciar uma nova simulação, faça a seguinte requisição:
$ curl -X POST "https://whatsapp.turn.io/v1/journeys/<uuid-da-jornada>/simulation" \
-H "Authorization: Bearer token" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "minha-sessao-001",
"revision": "production",
"contact": {
"name": "Usuário Teste",
"language": "eng"
}
}'
> {
"state": "waiting_for_input",
"message": "Bem-vindo! Qual é o seu nome?",
"context": {
"contact": {
"name": "Usuário Teste",
"language": "eng"
}
}
}
Parâmetros
| Nome | Obrigatório | Padrão | Descrição |
|---|---|---|---|
| simulation_id | sim | Um identificador de string fornecido pelo chamador (6 a 64 caracteres) para rastrear a sessão. | |
| revision | não | "production" | Qual revisão da jornada simular. Deve ser "production" ou "staging". |
| contact | não | {} | Um objeto com atributos de contato para preencher o contexto do contato simulado. Definir um campo reservado como null o limpa, o que é útil para simular contatos sem whatsapp_id. |
| parent | não | Um objeto exposto à jornada como @parent.*, para simular uma jornada do tipo função invocada por uma jornada pai. Variáveis reservadas (chat, contact, event, global, number, organisation) são ignoradas. | |
| child | não | Um objeto exposto à jornada como @child.*, para simular uma jornada retomando após o término de uma jornada filha. Variáveis reservadas são ignoradas, como em parent. | |
| input | não | A primeira mensagem do contato. A jornada responde logo após a abertura, e message traz as duas. | |
| http_mocks | não | [] | Respostas fixas para as requisições HTTP que as skills da jornada fazem, veja Simulando chamadas HTTP. |
| fail_unmocked_http_calls | não | false | Quando true, uma requisição que nenhum mock responde falha dentro da skill em vez de sair de verdade. |
Campos da resposta
| Nome | Descrição |
|---|---|
| simulation_id | O identificador da sessão de simulação. |
| state | O estado atual da simulação: "waiting_for_input" (a jornada espera uma resposta) ou "end" (a jornada terminou). |
| message | A saída de texto da jornada. Inclui conteúdo de mensagem, opções de botões e URLs de mídia. |
| card | O nome do card em que a jornada está após este turno. |
| options | Os rótulos dos botões ou itens de lista que a jornada ofereceu neste turno, por exemplo ["Yes, I agree", "No, thanks"]. O texto da mensagem também os lista, com instruções para um agente de IA que conduz a simulação. |
| skill_calls | As skills que os agentes de IA da jornada chamaram neste turno, em ordem, cada uma com name, arguments e output. |
| actions | As ações para as quais os agentes de IA da jornada encaminharam neste turno, por exemplo ["book_appointment"]. Vazio quando o agente continuou a conversa. |
| http_calls | As requisições HTTP que as skills da jornada fizeram neste turno, simuladas ou não, cada uma com method, url, headers, body, json (o corpo lido como JSON, quando for), query, params (os valores capturados pelos marcadores :nome do mock), status, mocked e failures. |
| contact | Os campos do contato simulado, os mesmos de context.contact, incluindo o que a jornada escreveu neles neste turno. |
| context | O contexto de variáveis atual da simulação, incluindo campos de contato e variáveis definidas pela jornada. Retornado a cada turno, então escritas de contato que acontecem após uma resposta são observáveis. Conexões de IA nele vêm sem o token do fornecedor. |
Testando uma jornada fora do Turn
Cada resposta diz o que a jornada fez naquele turno, então uma suíte de testes externa pode verificar mais do que o texto da resposta: em qual card a jornada está, quais skills os agentes de IA chamaram e com quais argumentos, para qual ação encaminharam e quais requisições HTTP as skills fizeram.
Simulando chamadas HTTP
Skills que chamam uma API por turn.http.request podem receber respostas fixas, para que um teste não dependa da API real e possa testar o que acontece quando ela falha. Cada mock aceita um method opcional, um padrão de url (* corresponde a qualquer coisa, :nome corresponde a um segmento do caminho e aparece em params da chamada) e a response a devolver (status é 200 por padrão, um body objeto é enviado como JSON):
$ curl -X POST "https://whatsapp.turn.io/v1/journeys/<journey-uuid>/simulation" \
-H "Authorization: Bearer token" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "booking-test-01",
"revision": "staging",
"fail_unmocked_http_calls": true,
"http_mocks": [
{
"method": "GET",
"url": "https://clinic.example.com/api/slots/:day",
"response": { "body": { "slots": ["09:00", "11:30"] } }
}
],
"input": "Can I come in on Monday?"
}'
> {
"state": "waiting_for_input",
"message": "Hi! How can I help you today?\nWe have Monday openings at 09:00 and 11:30. Which one would you like?",
"card": "Agent",
"skill_calls": [
{
"name": "find_slots",
"arguments": { "day": "monday" },
"output": "free slots on monday: 09:00, 11:30"
}
],
"actions": [],
"http_calls": [
{
"method": "GET",
"url": "https://clinic.example.com/api/slots/monday",
"params": { "day": "monday" },
"status": 200,
"mocked": true,
"failures": [],
...
}
],
"context": { ... }
}
Os mocks que correspondem respondem na ordem da lista, uma requisição cada, e o último que corresponde continua respondendo quando a lista acaba, então dois mocks na mesma url formam uma sequência. Uma requisição que nenhum mock responde sai de verdade e aparece em http_calls com mocked: false, a menos que fail_unmocked_http_calls seja true. Nesse caso ela falha dentro da skill e o motivo fica em failures da chamada.
http_mocks enviados com uma mensagem posterior são adicionados aos que a simulação já tem, então um teste pode mudar o que a API responde no meio de uma conversa.
Enviando entrada para uma simulação
Quando uma simulação está no estado waiting_for_input, envie a entrada do usuário fazendo outra requisição com o mesmo simulation_id:
$ curl -X POST "https://whatsapp.turn.io/v1/journeys/<uuid-da-jornada>/simulation" \
-H "Authorization: Bearer token" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "minha-sessao-001",
"input": "Santiago"
}'
> {
"state": "end",
"message": "Obrigado Santiago! Seu cadastro está completo."
}
Parâmetros
| Nome | Obrigatório | Descrição |
|---|---|---|
| simulation_id | sim | O mesmo ID de simulação usado ao iniciar a sessão. |
| input | sim | A resposta do usuário como uma string. Deve ser não vazia. |
| http_mocks | não | Mais mocks HTTP, adicionados aos que a simulação já tem. |
Exemplo de múltiplos turnos
Uma simulação típica de múltiplos turnos envolve iniciar a jornada e depois enviar entradas para cada etapa que requer uma resposta.
Etapa 1: Iniciar a jornada
$ curl -X POST "https://whatsapp.turn.io/v1/journeys/d144cb30-f759-4260-82ba-07353f0e389f/simulation" \
-H "Authorization: Bearer token" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "sessao-demo-01",
"revision": "production",
"contact": {
"name": "Maria",
"language": "eng"
}
}'
> {
"state": "waiting_for_input",
"message": "Olá Maria! Como podemos ajudá-la hoje?\n\nResponda exclusivamente com uma das seguintes opções:\nAgendar consulta\nVerificar status\nFalar com atendente",
"context": {
"contact": {
"name": "Maria",
"language": "eng"
}
}
}
Etapa 2: Enviar entrada do usuário
$ curl -X POST "https://whatsapp.turn.io/v1/journeys/d144cb30-f759-4260-82ba-07353f0e389f/simulation" \
-H "Authorization: Bearer token" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "sessao-demo-01",
"input": "Agendar consulta"
}'
> {
"state": "waiting_for_input",
"message": "Ótimo! Qual data funciona para você?"
}
Etapa 3: Continuar a conversa
$ curl -X POST "https://whatsapp.turn.io/v1/journeys/d144cb30-f759-4260-82ba-07353f0e389f/simulation" \
-H "Authorization: Bearer token" \
-H "Content-Type: application/json" \
-d '{
"simulation_id": "sessao-demo-01",
"input": "Próxima segunda-feira"
}'
> {
"state": "end",
"message": "Sua consulta está agendada para a próxima segunda-feira. Até lá!"
}
Respostas de erro
A API retorna códigos de status HTTP e mensagens de erro apropriados:
| Status | Erro | Descrição |
|---|---|---|
| 400 | simulation_id is required | O campo simulation_id está ausente no corpo da requisição. |
| 400 | simulation_id must be between 6 and 64 characters | O simulation_id é muito curto ou muito longo. |
| 400 | Invalid Journey UUID | O UUID da jornada na URL não é um UUID válido. |
| 400 | Journey has no published production revision | A jornada não possui uma revisão de produção publicada. |
| 400 | Journey has no staging revision | A jornada não possui uma revisão de staging (quando revision é "staging"). |
| 400 | Journey is disabled | A jornada está desativada. |
| 400 | Journey has been deleted | A jornada foi excluída. |
| 400 | Invalid revision, must be 'production' or 'staging' | Um valor inválido de revision foi fornecido. |
| 400 | Invalid parent, must be an object | O campo parent foi fornecido, mas não é um objeto. |
| 400 | Invalid child, must be an object | O campo child foi fornecido, mas não é um objeto. |
| 400 | Invalid input, must be a non-empty string | O campo input está vazio ou não é uma string. |
| 400 | Invalid http_mocks: ... | Um mock em http_mocks está malformado. A mensagem diz qual parte. |
| 404 | Journey not found | Nenhuma jornada existe com o UUID fornecido. |
| 404 | Simulation not found or expired | A sessão de simulação expirou ou não existe. |
Observações
- As sessões de simulação são efêmeras e expiram após um período de inatividade. Se uma sessão expirar, inicie uma nova com um
simulation_iddiferente. - Nenhuma mensagem real do WhatsApp é enviada durante uma simulação. A lógica da jornada é executada em memória.
- Conteúdo de mídia (imagens, vídeos, áudio, documentos) nas mensagens da jornada é retornado como texto descritivo com URLs, por exemplo:
"Image available at: https://...". - Quando a jornada inclui opções de botões ou listas, elas são adicionadas ao texto da mensagem, uma opção por linha.
- Campos de contato escritos pela jornada são refletidos em
contextno turno em que acontecem, então uma escrita que ocorre após uma resposta pode ser verificada diretamente, em vez de inferida a partir do texto da mensagem. - Blocos de agente de IA com memória ativada mantêm o contexto da conversa entre os turnos da simulação, permitindo que a IA faça referência a mensagens anteriores na sessão.
- As chamadas de IA que uma simulação faz não aparecem nos AI Logs, para que suítes de teste que repetem muitas conversas não os encham de sessões de teste.