Limites de taxa
Toda recusa por limite é um 429 com Retry-After (segundos). Nunca vira 5xx.
Os limites#
| Limite | Valor | Chave do limite | Resposta |
|---|---|---|---|
| Borda (envio, mídia, handoff, token de Flow) | 2.000 requisições a cada 10 s | chave de API × phone_number_id, por localidade da Cloudflare | 429 Graph, código 130429, Retry-After: 10 |
Gestão de webhooks (/v1/webhooks…) | 30 requisições a cada 60 s | chave de API | 429 { "error": "Rate limit hit" }, Retry-After: 60 |
| Vazão do número, mensagens automatizadas | 64 por segundo (rajada de 64) | número (todas as chaves juntas) | 429 Graph, código 130429, Retry-After em segundos (mínimo 1) |
| Vazão total do número | 80 por segundo, divididos com os atendentes | número | idem |
| Envios em andamento | 200 por número | número | idem |
| Volume do ciclo | o do plano (limite rígido) | organização | 429 Graph, código 1390006, Retry-After: 60 |
| Id novo na rota de envio e das Jornadas (Instagram ou WhatsApp?) | 30 ids novos por minuto | chave de API, por servidor | 429 Graph, código 130429, Retry-After: 1 (veja abaixo) |
| Respostas privadas a comentários (Instagram) | 10 % da fila e da vazão | conta | 429 Graph, código 130429, Retry-After: 60 com a fila cheia |
| Respostas privadas seguidas recusadas pela Meta (Instagram) | 20 em sequência | chave de API × conta | 429 Graph, código 130429, Retry-After até a pausa de 1 hora acabar |
| Envelopes de webhook de uma conta do Instagram | 60 por minuto | endpoint × conta | o excedente é descartado e contado |
Quando o número acabou de recusar por vazão, o 429 se repete por 200 ms (Retry-After: 1) sem consultar o número de novo.
O limite de volume do ciclo só sai com uma recarga, upgrade ou o próximo ciclo: esperar o Retry-After não o resolve. O details do erro traz o link da recarga.
Instagram#
O Instagram usa os mesmos limites do WhatsApp, com a conta no lugar do número: a borda (2.000 requisições a cada 10 s por chave × ig-user-id), a vazão da conta (as mensagens automatizadas dividem os 64 por segundo) e o volume do ciclo do plano. Em cima deles:
- Id novo. A chave
syn_só carrega a organização, então um{id}que o servidor ainda não conhece é tentado como conta do Instagram, uma vez. Cada chave tem 30 tentativas por minuto em cada servidor, e a tentativa volta quando o id é seu (uma conta do Instagram que respondeu, ou um número de WhatsApp da organização). Só um id que não é seu gasta a tentativa. Sem tentativa disponível, o envio de um número válido de WhatsApp acontece normalmente, e um id que a Synergy não reconhece recebe 429 comRetry-After: 1em vez de 404: sem a tentativa ela não sabe dizer que o id não existe. Repita depois de 1 segundo. Um envio de uma organização com dezenas de números, logo depois de uma atualização da plataforma, nunca recebe 404 por causa disso. - Resposta privada a comentário. A API usa no máximo 10 % da fila e da vazão que as Jornadas dividem com ela. Depois de 20 respostas seguidas que a Meta recusa por o comentário não ser elegível, a chave fica 1 hora sem resposta privada.
- Webhooks. Uma conta entrega no máximo 60 envelopes por minuto a cada endpoint (Instagram).
- Jornadas.
POST …/journeys/{journey_id}/runsobedece aos tetos da própria Jornada (por pessoa no dia, por hora, reentrada e o disjuntor): um teto atingido responde 429 comRetry-After: 1(Jornadas).
Cabeçalhos devolvidos#
A API devolve apenas Retry-After, e só nos 429. Não há cabeçalhos X-RateLimit-*: não é possível consultar o saldo restante. Controle a taxa do lado da integração.
Backoff#
- Em 429, espere os segundos de
Retry-After(no mínimo) antes de repetir. - Acrescente jitter aleatório para vários processos não voltarem juntos.
- Em 503 (código 2), repita com espera exponencial (1 s, 2 s, 4 s…) e um teto de tentativas.
- Ao repetir um envio, use
Idempotency-Keypara a mensagem chegar à Meta uma vez só. - Não repita 4xx que não sejam 429: o resultado será o mesmo.
curl
for i in 1 2 3 4 5; do
code=$(curl -s -o resposta.json -D cabecalhos.txt -w '%{http_code}' -X POST \
'https://api.synergyconnect.com.br/v25.0/106540352242922/messages' \
-H "Authorization: Bearer $SYNERGY_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: pedido-42-saiu' \
-d '{"messaging_product":"whatsapp","to":"5511999999999","type":"text","text":{"body":"oi"}}')
[ "$code" != "429" ] && break
espera=$(grep -i '^retry-after:' cabecalhos.txt | tr -dc '0-9')
sleep "${espera:-1}"
doneJavaScript
async function sendWithBackoff(url, init, attempts = 5) {
for (let i = 0; i < attempts; i++) {
const res = await fetch(url, init);
if (res.status !== 429 && res.status < 500) return res;
const retryAfter = Number(res.headers.get('Retry-After')) || 2 ** i;
const jitter = Math.random() * 500;
await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitter));
}
throw new Error('limite de tentativas');
}Python
import random
import time
import requests
def send_with_backoff(url, attempts=5, **kwargs):
for i in range(attempts):
res = requests.post(url, **kwargs)
if res.status_code != 429 and res.status_code < 500:
return res
wait = int(res.headers.get("Retry-After", 2**i))
time.sleep(wait + random.random() * 0.5)
raise RuntimeError("limite de tentativas")Ficou com dúvida? Escreva para suporte@synergyconnect.com.br.