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 (
TXTem_synergy-connect.<seu-domínio>) ou no arquivohttps://<seu-domínio>/.well-known/synergy-connect.txt. Aredirect_urlde 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 termanagementeonboarding).
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#
- O seu backend cria a sessão (
POST /v1/onboarding-sessions) e recebe umaurl. - Você redireciona o navegador do cliente para a
url(ou a abre num pop-up). - 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.
- A Synergy chama o seu webhook com
synergy_onboardinge leva o navegador de volta à suaredirect_url. - 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#
| Campo | Regra |
|---|---|
redirect_url | Obrigatório. Igual a uma URL de retorno cadastrada e com o domínio provado. https, sem usuário e senha, sem fragmento |
state | Obrigatório, até 512 caracteres. Um valor imprevisível por sessão, que você guarda e confere na volta |
mode | cloud (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 |
return | redirect (padrão) ou popup |
display_name | Obrigató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_ref | Opcional, até 128 caracteres: o seu id do cliente. Volta no webhook |
attach_to_key_ids | Opcional: chaves de API da organização que passam a alcançar o número novo. Nunca chaves de assistente |
expires_in | De 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:
| Status | Corpo | O 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 |
| 422 | reason: "return_url_not_registered" ou "return_url_unverified" | Cadastre a URL de retorno ou prove o domínio em Configurações → Desenvolvedor |
| 429 | reason: "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 |
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á{
"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.failedouonboarding.expired.- Em
failed,errortraz{ "reason": "…", "message": "…" }einstanceénull.finaldiz se a sessão acabou: um erro que o cliente pode tentar de novo (final: false) não encerra a sessão. entry[].idé owaba_idquando há; senão,"0".X-Synergy-Instance-Idvem só emcompleted.
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.
{
"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.
https://app.minha-plataforma.com/whatsapp/ok?session_id=obs_4Fq9Lm2Xr7Tz1Vb8Nc3Kd6Hs&state=e7f2c0d4-9b1e-4c52-8d0a-3f6a1b2c4d5e&status=completedwindow.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).
curl 'https://api.synergyconnect.com.br/v1/onboarding-sessions/obs_4Fq9Lm2Xr7Tz1Vb8Nc3Kd6Hs' \
-H "Authorization: Bearer $SYNERGY_API_KEY"{
"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.
curl -X POST 'https://api.synergyconnect.com.br/v1/onboarding-sessions/obs_4Fq9Lm2Xr7Tz1Vb8Nc3Kd6Hs/cancel' \
-H "Authorization: Bearer $SYNERGY_API_KEY"Estados da sessão#
status | Significa |
|---|---|
pending | O link vale e ainda não foi aberto |
opened | O cliente abriu a página (inclui a conclusão em andamento) |
completed | O número foi conectado: result traz os ids |
expired | O prazo (expires_in) acabou |
cancelled | Você cancelou, ou o cliente voltou sem conectar |
blocked | Sem 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.