Synergy Connect

Documentação

Cadastro hospedado de números

O cadastro hospedado deixa o seu cliente conectar o WhatsApp dele (o número e a conta do WhatsApp Business) à sua organização na Synergy, sem você montar o Embedded Signup da Meta. Você cria uma sessão, manda o navegador do cliente para uma página da Synergy, ele conclui o cadastro com a Meta, e você recebe o resultado por webhook.

Antes de começar#

Você precisa de:

  • Um plano pago com a feature api (não vale no teste grátis) e a conta em dia.
  • A liberação do cadastro hospedado para a sua organização. Peça em Configurações → Desenvolvedor; a plataforma analisa. Sem liberação, criar a sessão responde 403 com reason: "not_approved".
  • Uma ou mais URLs de retorno cadastradas em Configurações → Desenvolvedor, com o domínio provado: a Synergy procura um código no DNS (TXT em _synergy-connect.<seu-domínio>) ou no arquivo https://<seu-domínio>/.well-known/synergy-connect.txt. A redirect_url de cada sessão precisa ser igual, byte a byte, a uma URL cadastrada.
  • Uma chave de API com o escopo onboarding (Autenticação).
  • Um webhook que assine o evento synergy_onboarding (a chave que o cria precisa ter management e onboarding).

A cota de cadastros por semana e o teto por ciclo dependem da liberação e do plano. Quando não há vaga de número, a sessão nasce blocked e nada é gasto.

Como funciona#

  1. O seu backend cria a sessão (POST /v1/onboarding-sessions) e recebe uma url.
  2. Você redireciona o navegador do cliente para a url (ou a abre num pop-up).
  3. A página da Synergy mostra o nome da sua plataforma, o nome verificado da sua organização na Synergy e o endereço para onde o cliente volta. Ele escolhe o modo, conecta com a Meta e conclui.
  4. A Synergy chama o seu webhook com synergy_onboarding e leva o navegador de volta à sua redirect_url.
  5. Seu backend confirma o resultado (pelo webhook ou por GET /v1/onboarding-sessions/{id}) e só então libera o recurso.

O redirecionamento do navegador é só conveniência: o cliente pode fechar a aba. O resultado autoritativo é o webhook ou o GET.

1. Criar a sessão#

CampoRegra
redirect_urlObrigatório. Igual a uma URL de retorno cadastrada e com o domínio provado. https, sem usuário e senha, sem fragmento
stateObrigatório, até 512 caracteres. Um valor imprevisível por sessão, que você guarda e confere na volta
modecloud (número novo na API oficial), coexistence (número que o cliente já usa no app WhatsApp Business) ou choice (o cliente escolhe na página). Padrão choice
returnredirect (padrão) ou popup
display_nameObrigatório, de 1 a 60 caracteres: é o nome que o cliente vê ("Minha Plataforma quer conectar o seu WhatsApp"). Nomes com synergy, meta, whatsapp, facebook, instagram, oficial, official, verificado ou verified (sem acento, em qualquer caixa) são recusados com issues[].message = "reserved"
external_refOpcional, até 128 caracteres: o seu id do cliente. Volta no webhook
attach_to_key_idsOpcional: chaves de API da organização que passam a alcançar o número novo. Nunca chaves de assistente
expires_inDe 300 a 3600 segundos. Padrão 900

Resposta 201:

A url só aparece enquanto a sessão está pending. Ela serve para um navegador: se o cliente abrir o link em outro navegador depois, a página recusa ("link já aberto em outro navegador"). Crie uma sessão por tentativa.

Sem vaga de número no plano, a resposta é 201 com status: "blocked" e blocked: { "reason": "plan_limit", "message": "…" }; os outros motivos são account_paused e onboarding_cap. Mostre ao cliente a mensagem e resolva pelo plano.

Recusas na criação:

StatusCorpoO que fazer
400{ "error": "Invalid body", "issues": [...] }Corrija os campos de issues
403{ "error": "…", "reason": "not_approved" } (ou suspended, trialing, invalid_keys)Peça a liberação, saia do teste grátis ou corrija attach_to_key_ids
403{ "error": "…", "feature": "api" }O plano não inclui a API
422reason: "return_url_not_registered" ou "return_url_unverified"Cadastre a URL de retorno ou prove o domínio em Configurações → Desenvolvedor
429reason: "quota_exceeded", "app_limit" ou "too_many_open" (ou Rate limit hit)Cota semanal da organização, limite semanal da plataforma na Meta, ou mais de 50 sessões abertas: cancele as que não usa. Respeite o Retry-After quando houver
Exemplo

curl

curl -X POST 'https://api.synergyconnect.com.br/v1/onboarding-sessions' \
  -H "Authorization: Bearer $SYNERGY_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "redirect_url": "https://app.minha-plataforma.com/whatsapp/ok",
    "state": "e7f2c0d4-9b1e-4c52-8d0a-3f6a1b2c4d5e",
    "mode": "choice",
    "return": "redirect",
    "display_name": "Minha Plataforma",
    "external_ref": "tenant_8841",
    "expires_in": 900
  }'

JavaScript

const res = await fetch('https://api.synergyconnect.com.br/v1/onboarding-sessions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SYNERGY_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    redirect_url: 'https://app.minha-plataforma.com/whatsapp/ok',
    state: stateDaSessaoDoCliente, // guarde no seu banco, ligado ao cliente
    mode: 'choice',
    display_name: 'Minha Plataforma',
    external_ref: 'tenant_8841',
  }),
});
const session = await res.json();
// redirecione o navegador do cliente: res.redirect(session.url)

Python

import os

import requests

res = requests.post(
    "https://api.synergyconnect.com.br/v1/onboarding-sessions",
    headers={"Authorization": f"Bearer {os.environ['SYNERGY_API_KEY']}"},
    json={
        "redirect_url": "https://app.minha-plataforma.com/whatsapp/ok",
        "state": state_da_sessao_do_cliente,
        "mode": "choice",
        "display_name": "Minha Plataforma",
        "external_ref": "tenant_8841",
    },
)
session = res.json()  # session["url"]: mande o navegador do cliente para lá
JSON
{
  "id": "obs_4Fq9Lm2Xr7Tz1Vb8Nc3Kd6Hs",
  "status": "pending",
  "url": "https://app.synergyconnect.com.br/connect#obl_org_7Hk2pQ9_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_bbbbbbbbbb",
  "mode": "choice",
  "return": "redirect",
  "redirect_url": "https://app.minha-plataforma.com/whatsapp/ok",
  "state": "e7f2c0d4-9b1e-4c52-8d0a-3f6a1b2c4d5e",
  "external_ref": "tenant_8841",
  "display_name": "Minha Plataforma",
  "created_at": "2026-01-01T00:00:00.000Z",
  "expires_at": "2026-01-01T00:15:00.000Z",
  "blocked": null,
  "last_error": null,
  "result": null
}

2. O cliente na página#

Redirecione (302) o navegador para session.url. Com return: "popup", abra a url com window.open e escute a mensagem de volta (veja abaixo). A página:

  • mostra quem pede a conexão e para onde o cliente volta, em letras grandes;
  • em choice, pede para escolher entre número novo e número já usado no app WhatsApp Business;
  • em coexistência, tem uma caixa desmarcada para trazer o histórico de conversas: é uma escolha do cliente, separada;
  • avisa que o acesso concedido pode ser removido depois no WhatsApp Manager, em Parceiros;
  • tem sempre um botão Voltar sem conectar.

3. O resultado: webhook synergy_onboarding#

Ao concluir, falhar ou expirar, a Synergy envia o evento aos seus webhooks que assinam synergy_onboarding (e cujo instanceIds é null ou, em completed, contém o número novo). A entrega é assinada (Assinatura dos webhooks), tem retentativa por até 24 h e X-Synergy-Delivery-Id estável.

  • event: onboarding.completed, onboarding.failed ou onboarding.expired.
  • Em failed, error traz { "reason": "…", "message": "…" } e instance é null. final diz se a sessão acabou: um erro que o cliente pode tentar de novo (final: false) não encerra a sessão.
  • entry[].id é o waba_id quando há; senão, "0".
  • X-Synergy-Instance-Id vem só em completed.

Motivos de falha (error.reason): cancelled_by_user, meta_cancelled, meta_error, plan_limit, account_paused, trial_business_used, waba_in_use, number_in_use, number_not_in_waba, number_not_found, meta_unavailable, app_limit, quota_exceeded, onboarding_cap, not_approved, suspended, too_many_attempts, expired e error.

Para guardar o phone_number_id e começar a enviar, use o instance do evento. Para criar o webhook, veja Webhooks.

JSON
{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "524126980791429",
    "time": 1767225600,
    "changes": [{
      "field": "synergy_onboarding",
      "value": {
        "event": "onboarding.completed",
        "session_id": "obs_4Fq9Lm2Xr7Tz1Vb8Nc3Kd6Hs",
        "state": "e7f2c0d4-9b1e-4c52-8d0a-3f6a1b2c4d5e",
        "external_ref": "tenant_8841",
        "final": true,
        "error": null,
        "instance": {
          "id": "3f9a1c0b7d2e4a6f8b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a",
          "phone_number_id": "106540352242922",
          "waba_id": "524126980791429",
          "display_phone_number": "+55 11 91234-5678",
          "mode": "cloud"
        },
        "timestamp": "1767225600"
      }
    }]
  }]
}

4. A volta do navegador#

Com return: "redirect", o cliente volta para a redirect_url com estes parâmetros na query:

status é completed, cancelled, expired ou blocked; em caso de erro, vem também error=<motivo>. A Synergy nunca manda code, token nem telefone na URL.

Com return: "popup", a página fecha e manda ao window.opener (apenas para a origem da sua redirect_url):

Em celulares sem opener, a página cai no redirect.

Texto
https://app.minha-plataforma.com/whatsapp/ok?session_id=obs_4Fq9Lm2Xr7Tz1Vb8Nc3Kd6Hs&state=e7f2c0d4-9b1e-4c52-8d0a-3f6a1b2c4d5e&status=completed
Exemplo
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://app.minha-plataforma.com') return;
  if (event.data?.type !== 'synergy_onboarding') return;
  const { session_id, state, status, error } = event.data;
  // confirme no servidor antes de liberar
});

5. Confirmar antes de liberar#

O navegador é controlado pelo cliente: não libere nada só porque a URL disse status=completed. Confirme no servidor, comparando o state que você guardou com o da sessão:

Regra de ouro: libere o recurso do cliente somente se status é completed e o state bate com o que você guardou para aquele cliente. Em 404 { "error": "Session not found" }, a sessão não existe (também vale para o id de outra organização). O SDK terá um atalho, onboarding.verifyReturn, que faz essa consulta e comparação (SDK para JavaScript e TypeScript).

Exemplo
curl 'https://api.synergyconnect.com.br/v1/onboarding-sessions/obs_4Fq9Lm2Xr7Tz1Vb8Nc3Kd6Hs' \
  -H "Authorization: Bearer $SYNERGY_API_KEY"
JSON
{
  "id": "obs_4Fq9Lm2Xr7Tz1Vb8Nc3Kd6Hs",
  "status": "completed",
  "url": null,
  "state": "e7f2c0d4-9b1e-4c52-8d0a-3f6a1b2c4d5e",
  "external_ref": "tenant_8841",
  "last_error": null,
  "result": {
    "instance_id": "3f9a1c0b7d2e4a6f8b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a",
    "phone_number_id": "106540352242922",
    "waba_id": "524126980791429",
    "display_phone_number": "+55 11 91234-5678",
    "mode": "cloud"
  }
}

Cancelar#

Devolve a sessão com status: "cancelled"; é idempotente. Uma sessão já completed não cancela (409): o número já foi conectado.

Exemplo
curl -X POST 'https://api.synergyconnect.com.br/v1/onboarding-sessions/obs_4Fq9Lm2Xr7Tz1Vb8Nc3Kd6Hs/cancel' \
  -H "Authorization: Bearer $SYNERGY_API_KEY"

Estados da sessão#

statusSignifica
pendingO link vale e ainda não foi aberto
openedO cliente abriu a página (inclui a conclusão em andamento)
completedO número foi conectado: result traz os ids
expiredO prazo (expires_in) acabou
cancelledVocê cancelou, ou o cliente voltou sem conectar
blockedSem vaga de número, conta pausada ou teto do ciclo

O cliente que concluiu fica com o número na sua organização: ele conta nas vagas do plano e nas regras de uso da API.

Ficou com dúvida? Escreva para suporte@synergyconnect.com.br.