Criar um webhook
Cadastra uma URL para receber eventos. A URL precisa se provar antes: por padrão (verification: "ping") com um POST de teste assinado que ela responde com 2xx; ou com o handshake hub.challenge da Meta (verification: "meta", com verifyToken), para receptores feitos para a Meta.
A resposta traz o secret que assina as entregas (X-Hub-Signature-256). Ele só aparece aqui e em rotate-secret.
Uma chave limitada a alguns números só cadastra webhooks restritos a números dela (instanceIds). Limite de 20 webhooks por organização. Corpo de até 16 KB.
Requer#
- Escopo da chave
- Gerenciar webhooks
managementComo os escopos funcionam - Recurso do plano
- API e webhooks
apiVer os planos que incluem - Formato
- Próprio do Synergy. Erros em
{ "error": "…" }.
Parâmetros#
Cabeçalhos#
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| Authorization | string | obrigatório | Sua chave de API, no formato Bearer <chave>. Veja Autenticação. |
Corpo#
Tipo de conteúdo: application/json.
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| url | string | obrigatório | A URL que recebe os eventos: https, pública, sem usuário e senha, e nunca um endereço da própria Synergy. |
| fields | string[] | obrigatório | Os eventos que o webhook recebe: os nomes da Meta, mais statuses (que a Meta entrega dentro de messages e aqui é uma escolha à parte) e synergy_conversations. Veja webhooks neste documento. |
| statuses | string[] | null | opcional | Filtro dentro de statuses: só estes status chegam. null = todos. |
| instanceIds | string[] | null | obrigatório | Os números que o webhook cobre, pelo id do número na Synergy (64 hex; o mesmo de X-Synergy-Instance-Id). null = todos os números da organização, inclusive os conectados depois. Uma chave limitada a alguns números precisa listar números dela. |
| name | string | null | opcional | |
| verification | string | opcional | ping (padrão): a URL responde 2xx a um POST assinado de teste (synergy_ping). meta: a URL responde ao GET hub.challenge da Meta, e verifyToken é obrigatório. Valores: ping, meta. |
| verifyToken | string | opcional | O token que a URL confere no handshake da Meta (hub.verify_token). Obrigatório com verification: "meta". Não é guardado. |
| headers | object | opcional | Headers próprios enviados em toda entrega (por exemplo Authorization): até 10, nomes só com letras, dígitos e hífen (1 a 64 caracteres), sem repetir e fora dos reservados (host, content-length, content-type, transfer-encoding, connection, keep-alive, upgrade, te, trailer, proxy-authorization, proxy-connection, user-agent, x-hub-signature-256, x-hub-signature, x-synergy-*). Os valores nunca voltam em nenhuma resposta: só os nomes, em headerNames. |
Respostas#
| Status | Código | Quando acontece |
|---|---|---|
| 201 | — | Webhook criado |
| 400 | — | O corpo não passa na validação (issues traz os problemas, no formato do zod) |
| 400 | — | A URL não é aceita |
| 400 | — | A URL não respondeu à verificação |
| 400 | — | Chave limitada a alguns números |
| 400 | — | Limite de webhooks |
| 401 | — | Chave revogada ou desconhecida |
| 401 | 190 | Chave malformada ou forjada |
| 403 | — | A chave não tem o escopo management |
| 403 | — | O plano da organização não inclui a API |
| 403 | — | A conta está em modo leitura ou suspensa por falta de pagamento |
| 413 | — | Corpo grande demais |
| 429 | — | Limite de taxa |
Os erros no formato da Meta trazem o código em error.code. Ver todos os erros.
Corpo da resposta 201#
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| row | object | obrigatório | |
| row.id | string | obrigatório | |
| row.url | string | obrigatório | |
| row.fields | string[] | obrigatório | |
| row.statuses | string[] | null | obrigatório | |
| row.instanceIds | string[] | null | obrigatório | |
| row.enabled | boolean | obrigatório | |
| row.verification | string | obrigatório | Valores: meta, ping. |
| row.headerNames | string[] | null | obrigatório | Os NOMES dos headers próprios; os valores nunca voltam. |
| row.disabledReason | string | null | obrigatório | |
| row.createdBy | string | obrigatório | O id do usuário que criou, ou key:<id da chave> quando foi pela API. |
| row.createdAt | integer | obrigatório | Quando foi criado (ms desde 1970). |
| row.updatedAt | integer | obrigatório | A versão do webhook: toda alteração muda, e recomeça a saúde dele em cada número (ms desde 1970). |
| row.name | string | null | obrigatório | Um rótulo seu (o node do n8n usa n8n · <workflow> · prod|test). Opcional. |
| secret | string | obrigatório | A chave do HMAC de X-Hub-Signature-256. Só aparece aqui e em rotate-secret: guarde. |
| pending | integer | obrigatório | Quantos números ainda não receberam esta alteração. A Synergy repete o envio sozinha; 0 = já vale em todos. |
Limites de taxa#
- Gestão: 30 requisições a cada 60 s por chave (API_MGMT_LIMIT).
Notas#
- Um corpo que não é JSON é tratado como corpo vazio: responde 400
Invalid body. - O
Content-Typeda requisição não é conferido. - As recusas de regra (URL, limite, números, verificação da URL) voltam em 400 com
errorem português — exceto a de alcance da chave, em inglês.
curl
curl -X POST 'https://api.synergyconnect.com.br/v1/webhooks' \
-H "Authorization: Bearer $SYNERGY_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url":"https://hooks.example.com/synergy","fields":["messages","statuses"],"instanceIds":null,"headers":{"Authorization":"Bearer meu-token"}}'JavaScript
const res = await fetch('https://api.synergyconnect.com.br/v1/webhooks', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SYNERGY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://hooks.example.com/synergy",
"fields": [
"messages",
"statuses"
],
"instanceIds": null,
"headers": {
"Authorization": "Bearer meu-token"
}
}),
});
console.log(res.status, await res.json());Python
import os
import requests
res = requests.post(
"https://api.synergyconnect.com.br/v1/webhooks",
headers={
"Authorization": f"Bearer {os.environ['SYNERGY_API_KEY']}",
},
json={
"url": "https://hooks.example.com/synergy",
"fields": [
"messages",
"statuses",
],
"instanceIds": None,
"headers": {
"Authorization": "Bearer meu-token",
},
},
)
print(res.status_code, res.json())201
{
"row": {
"id": "7c1f0c7e-4b53-4c7b-9a51-3f6f0a2d9e10",
"url": "https://hooks.example.com/synergy",
"fields": [
"messages",
"statuses"
],
"statuses": null,
"instanceIds": null,
"enabled": true,
"verification": "ping",
"headerNames": [
"Authorization"
],
"disabledReason": null,
"createdBy": "key:0b9c4d1e-52a7-4f6b-8c3d-1e2f3a4b5c6d",
"createdAt": 1767225600000,
"updatedAt": 1767225600000,
"name": null
},
"secret": "9f2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e",
"pending": 0
}400
{
"error": "Invalid body",
"issues": [
{
"code": "invalid_type",
"expected": "array",
"path": [
"fields"
],
"message": "Invalid input: expected array, received undefined"
}
]
}Há 5 casos com este status; veja a tabela de respostas.
401
{
"error": "Invalid or revoked key"
}Há 2 casos com este status; veja a tabela de respostas.
403
{
"error": "This key lacks the management scope"
}Há 3 casos com este status; veja a tabela de respostas.
413
{
"error": "The body is too large (16 KB max)"
}429
{
"error": "Rate limit hit"
}Ficou com dúvida? Escreva para suporte@synergyconnect.com.br.