Template livre#
Alguns destinos esperam um formato próprio: o webhook de entrada de um CRM que quer {"nome": …, "telefone": …}, um sistema legado, um formulário. Com format: "custom", você escreve o corpo do pedido como um template, e nós o preenchemos com os dados de cada evento.
Exemplo#
{
"name": "CRM: novos agendamentos",
"url": "https://crm.example.com/api/leads",
"events": ["appointment.created"],
"format": "custom",
"template": {
"method": "POST",
"content_type": "application/json",
"body": "{\"nome\": {{ data.object.client.name | json }}, \"telefone\": {{ data.object.client.phone | json }}, \"quando\": {{ data.object.start | date('d/m/Y H:i') | json }}, \"evento\": {{ type | json }}}",
"headers": { "Authorization": "Bearer {{ secret_ref('crm_token') }}" },
"query": { "origem": "guruagenda" }
},
"secrets": { "crm_token": "o-token-do-seu-crm" }
}
O corpo, com um agendamento da Maria Souza, chega assim:
{"nome": "Maria Souza", "telefone": "+5511999999999", "quando": "20/10/2026 14:30", "evento": "appointment.created"}
Linguagem#
Os templates usam a sintaxe do Jinja: {{ … }} insere um valor, {% if … %} e {% for … %} controlam o texto.
Variáveis disponíveis:
| Variável | O que é |
|---|---|
id, type, created_at, api_version |
Dados do evento. |
data.object |
O objeto completo (agendamento, cliente, sinal…), como na API. |
data.changes |
O que mudou, com from e to. |
context.actor, context.source |
Quem causou o evento e por onde. |
organization.name, organization.timezone |
A sua organização. |
now |
Data e hora da entrega. |
secret_ref('nome') |
Um segredo guardado no webhook (veja abaixo). |
Filtros: json, date, default, upper, lower, truncate, replace, join, first, last e length. O date formata datas com d (dia), m (mês), Y (ano com 4 dígitos), y (2 dígitos), H (hora), i (minuto) e s (segundo), no fuso da organização: {{ data.object.start | date('d/m/Y H:i') }}.
Corpo em JSON: sempre | json#
Com content_type: "application/json", toda inserção {{ … }} no corpo precisa terminar no filtro json, e o valor vai sem aspas em volta: o filtro já gera o texto entre aspas, escapa caracteres especiais e escreve null quando o valor não existe.
"nome": {{ data.object.client.name | json }}
Assim, um nome com aspas ou quebra de linha nunca quebra o JSON. Ao salvar, montamos o corpo com um evento de exemplo e recusamos o template se o resultado não for JSON válido.
Outros formatos#
content_type |
body |
|---|---|
application/json (padrão) |
Texto do template; o resultado precisa ser JSON. |
application/x-www-form-urlencoded |
Objeto campo → template, por exemplo {"nome": "{{ data.object.client.name }}"}. |
text/plain |
Texto livre. |
method pode ser POST (padrão), PUT ou PATCH. headers e query aceitam até 10 itens cada, também como template.
Segredos#
Tokens do destino não devem ficar no texto do template, porque o template aparece de volta na API e na tela. Guarde-os em secrets (até 10, nomes com letras minúsculas, números e _) e use {{ secret_ref('nome') }}. Os valores ficam cifrados e nunca voltam: a API mostra só secret_names, e o histórico de entregas mostra •••• no lugar deles. No PATCH, os segredos enviados se juntam aos existentes; mande null para apagar um.
Limites e erros#
- Template com até 16 KB; resultado com até 64 KB, montado em até 100 ms.
- Não são permitidos
macro,call,include,import,extends, nem mais de 2 laços um dentro do outro. - Template inválido ao salvar responde
422template_invalid, com o campo emerrors[]. - Se um template falhar na entrega (um valor que estourou um limite, por exemplo), a entrega falha na hora, sem novas tentativas, e a falha aparece no histórico.
As entregas do formato custom são assinadas como as do formato genérico: veja Verificar a assinatura.