Idempotência e limites de taxa
Torne as mutações da API do Tokeflow seguras para repetição com chaves de idempotência e projete clientes resilientes que respeitem os limites de taxa por chave.
Redes falham. Requisições atingem o tempo limite. Clientes fazem novas tentativas. Em qualquer superfície de pagamento, uma nova tentativa que cria silenciosamente uma segunda cobrança é um incidente em produção.
O Tokeflow oferece dois primitivos para você construir integrações resilientes:
- Idempotência — repita com segurança as mutações de criação e cobrança sem duplicar o trabalho.
- Limites de taxa — throughput previsível por chave, com sinais claros para que seu cliente possa recuar de forma controlada.
Esta página cobre ambos, além de um helper de nova tentativa pronto para copiar e colar.
O Tokeflow é a camada de orquestração. Ele padroniza a superfície da API, o roteamento e a semântica de replay descritos aqui; os provedores de pagamento conectados realizam o processamento e a liquidação de fato.
Idempotência
Por que a idempotência importa
Considere um checkout que chama POST /api/v1/transactions. A requisição chega ao Tokeflow, a cobrança é iniciada a jusante, mas a conexão cai antes que a resposta volte ao seu servidor. Seu cliente não sabe se a cobrança foi bem-sucedida. Se ele repetir cegamente, você corre o risco de cobrar o cliente duas vezes.
Uma chave de idempotência elimina essa ambiguidade. Você anexa uma chave única à mutação. O Tokeflow registra o resultado associado a essa chave. Se a mesma chave chegar novamente — seja de uma nova tentativa automática, de um duplo clique ou de uma reentrega de fila — o Tokeflow retorna o resultado original em vez de executar a operação uma segunda vez.
A chave de idempotência
Forneça a chave de uma de duas maneiras. O cabeçalho tem precedência se ambos estiverem presentes.
| Local | Field | Notas |
|---|---|---|
| Cabeçalho HTTP | Idempotency-Key: <unique> | Recomendado. Não diferencia maiúsculas de minúsculas. |
| Corpo da requisição | "idempotency_key": "<unique>" | Útil quando o cabeçalho é difícil de definir (por exemplo, alguns relays de fila/webhook). |
O valor é uma string opaca que você gera. Ela deve ser única por operação lógica — ou seja, a mesma chave deve mapear para exatamente uma ação pretendida.
:::tip Construção recomendada da chave Derive a chave de forma determinística a partir da operação de negócio, não aleatoriamente. Um bom padrão combina um identificador de negócio estável com um contador de tentativas:
order_12345_attempt_1cart-a1b2c3:checkout- Um UUID v4 gerado uma vez por operação lógica e reutilizado ao longo das novas tentativas dessa operação.
A regra: toda nova tentativa da mesma ação pretendida reutiliza a mesma chave. Uma ação genuinamente nova (por exemplo, o cliente escolhe pagar novamente após uma falha) recebe uma nova chave. :::
Não gere uma nova chave aleatória a cada tentativa HTTP. Isso anula o mecanismo — cada tentativa seria tratada como uma cobrança nova e distinta.
Semântica de replay
A idempotência tem escopo por merchant e por chave. A primeira requisição com uma dada chave é executada normalmente; requisições subsequentes com a mesma chave reproduzem o resultado armazenado.
| Cenário | Status HTTP | Corpo |
|---|---|---|
Primeira requisição com a chave K | 201 Created | O recurso criado |
Replay com a mesma chave K | 200 OK | O mesmo corpo da primeira resposta |
O código de status é o sinal: 201 significa que você criou algo novo, 200 significa que você atingiu um replay. O corpo da resposta é idêntico em ambos os casos, então seu código pode tratá-los de forma uniforme e usar o status apenas para observabilidade.
O replay retorna a resposta original armazenada — ele não reexecuta o roteamento nem contata o provedor novamente. Um 200 de replay é a garantia de que nenhuma segunda cobrança ocorreu.
Quais endpoints aceitam chaves de idempotência
Use chaves de idempotência em mutações de criação e cobrança — qualquer POST que tenha um efeito colateral que você não gostaria de duplicar. A mais importante é a criação de uma transação:
curl -X POST https://api.tokeflow.com/api/v1/transactions \
-H "Authorization: Bearer sk_live_mer_xxx" \
-H "Idempotency-Key: order_12345_attempt_1" \
-H "Content-Type: application/json" \
-d '{
"amount": 15000,
"currency": "BRL",
"payment_method": "credit_card",
"customer_id": "cust_7h2k9",
"payment_instrument_id": "pi_4a1c8",
"capture": true,
"metadata": { "campaign": "black_friday" }
}'Primeira resposta (201 Created):
{
"success": true,
"data": {
"id": "tx_9a8b7c6d",
"status": "authorized",
"amount": 15000,
"currency": "BRL",
"payment_method": "credit_card",
"charge_type": "payment",
"customer_id": "cust_7h2k9",
"metadata": { "campaign": "black_friday" },
"created_at": "2026-01-15T12:30:00.000Z"
},
"request_id": "req_8f3c2a91",
"timestamp": "2026-01-15T12:30:00.000Z"
}Repetir a mesma Idempotency-Key retorna o data idêntico com HTTP 200 OK.
Idempotency-Key é obrigatória em POST /api/v1/transactions. Omiti-la retorna 400 validation_error. Isso é intencional — a criação de pagamento deve sempre ser segura para replay.
{
"error": {
"type": "validation_error",
"code": "IDEMPOTENCY_KEY_REQUIRED",
"message": "Idempotency key is required. Provide it via Idempotency-Key header or idempotency_key in request body.",
"details": {},
"request_id": "req_1b2c3d4e",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Orientações sobre TTL
As chaves de idempotência são retidas por uma janela limitada, não para sempre. Trate a garantia como cobrindo o tempo de vida ativo de uma operação e suas novas tentativas — normalmente de minutos a horas, bem dentro de qualquer orçamento razoável de novas tentativas.
Implicações práticas:
- Repita uma operação prontamente. Não reutilize uma chave dias depois e espere um replay.
- Para uma tentativa genuinamente nova após um longo intervalo, cunhe uma nova chave.
- Nunca dependa de chaves de idempotência como um armazenamento de deduplicação de longo prazo. Use seus próprios registros de pedido/transação para isso.
Consulte Erros para a referência completa de tipos de erro e Transações para o contrato de criação de transação.
Limites de taxa
Como os limites são contados
Todo limite é uma contagem de requisições em uma janela de 60 segundos, mantida por credencial e por endpoint: cada chave de API (ou chave pública, no caso do Bridge SDK) tem seu próprio orçamento em cada rota, então uma rajada em um endpoint nunca consome o orçamento de outro, e uma chave de Organization e uma chave de Merchant nunca compartilham um orçamento. O teto depende do tipo de tráfego que a rota recebe.
| Classe de rota | Rotas | Limite padrão por chave, por rota |
|---|---|---|
| Pagamento | Escritas que movimentam dinheiro e alteram estado: criação de transações, capturas, cancelamentos e reembolsos; sessões de checkout e seus itens; clientes, instrumentos de pagamento e ações de assinatura. | 600 requisições / minuto |
| Leitura | Listagens e consultas: transações, reembolsos, pedidos, sessões de checkout, clientes, instrumentos de pagamento, assinaturas e suas transições. | 600 requisições / minuto |
| Eventos de checkout | POST /checkout-sessions/:id/events. Também limitada por sessão de checkout, para que uma única sessão ruidosa não esgote a chave. | 1.200 requisições / minuto por chave; 60 / minuto por sessão |
| Bridge SDK | Rotas que o navegador chama com uma chave pública — sessões, tokens, tokens de pagador, contexto de checkout. Também limitada por IP do cliente, para que um único visitante não esgote o orçamento do merchant. | 600 requisições / minuto por chave pública |
| Verificação de cartão (SDK) | A verificação de cartão de valor zero do SDK. Mantida baixa e separada, porque é o alvo de quem testa cartões. | 60 requisições / minuto por chave pública |
| Todo o resto | Rotas sem classe. | 100 requisições / minuto |
POST /checkout-sessions/:id/identify é uma rota da classe de pagamento que também é limitada por sessão de checkout.
Esses são os padrões. Se uma carga de trabalho precisar de mais — importações em massa, checkouts de alto volume, jobs de reconciliação — entre em contato com a equipe da Tokeflow: os limites podem ser aumentados para uma chave específica, por classe de rota. Uma chave revogada ou expirada perde seu orçamento imediatamente.
Quando você é limitado
Uma requisição limitada é rejeitada com 429 Too Many Requests antes de chegar ao recurso. A resposta informa qual orçamento a bloqueou e quando voltar:
| Cabeçalho | Significado |
|---|---|
X-RateLimit-Limit | O limite do orçamento que bloqueou a requisição. |
X-RateLimit-Remaining | 0 em uma requisição rejeitada. |
X-RateLimit-Reset | Segundos até o contador desse orçamento zerar. |
Retry-After | Segundos até você poder tentar novamente. |
{
"error": {
"type": "rate_limit_error",
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please retry after a short delay.",
"details": { "retry_after_seconds": 12, "limit": 600 },
"request_id": "req_9z8y7x6w",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Projetando para limites de taxa
Um cliente robusto trata o 429 como um sinal esperado e recuperável, em vez de uma falha.
- Respeite o
Retry-After. Quando um429o trouxer, espere pelo menos esse número de segundos antes da próxima tentativa. - Recue exponencialmente. Espere progressivamente mais tempo entre as tentativas (por exemplo, 0,5s, 1s, 2s, 4s) em vez de martelar imediatamente.
- Adicione jitter. Aleatorize cada atraso para que clientes concorrentes não repitam em sincronia e voltem a colidir ("thundering herd").
- Limite as tentativas. Desista após um número limitado de novas tentativas e exponha o erro.
- Combine com idempotência. Ao repetir um
POSTapós um429, reutilize a mesmaIdempotency-Keypara que o eventual sucesso não possa gerar cobrança dupla. - Distribua a carga. Faça em lote onde a API suportar e evite loops de polling apertados — prefira webhooks para mudanças de estado.
Nova tentativa com backoff exponencial (Node)
Este helper repete em 429 (e 5xx transitório) usando backoff exponencial com jitter total. Como ele encaminha os mesmos cabeçalhos — incluindo Idempotency-Key — em cada tentativa, as mutações repetidas permanecem seguras.
/**
* fetch wrapper with exponential backoff + jitter for 429 / 5xx.
* Pass a stable Idempotency-Key in `init.headers` so retried POSTs are replay-safe.
*/
async function tokeflowFetch(url, init = {}, { maxRetries = 5, baseMs = 500 } = {}) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, init);
const retryable = res.status === 429 || (res.status >= 500 && res.status <= 599);
if (!retryable || attempt >= maxRetries) {
return res; // success, non-retryable error, or out of attempts
}
// Exponential backoff with full jitter: random delay in [0, base * 2^attempt].
const delay = Math.floor(Math.random() * baseMs * 2 ** attempt);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
// Usage — idempotency key generated once and reused across all retries.
const res = await tokeflowFetch("https://api.tokeflow.com/api/v1/transactions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEFLOW_SECRET_KEY}`,
"Idempotency-Key": "order_12345_attempt_1",
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 15000,
currency: "BRL",
payment_method: "credit_card",
customer_id: "cust_7h2k9",
payment_instrument_id: "pi_4a1c8",
capture: true,
}),
});
const { data } = await res.json();Mantenha a chave secreta (sk_*) apenas no servidor. A lógica de nova tentativa acima pertence ao seu backend. Nunca incorpore uma chave secreta em código de cliente de navegador ou mobile.
Juntando tudo
| Objetivo | Use |
|---|---|
| Evitar cobranças duplicadas em nova tentativa | Idempotency-Key em POST /api/v1/transactions |
| Distinguir resultado novo de replay | 201 (novo) vs 200 (replay) |
Sobreviver a rajadas e 429s | Backoff exponencial + jitter |
| Evitar cobrança dupla durante o backoff | Mesma chave de idempotência em todas as novas tentativas |
Leitura relacionada:
- Autenticação — tipos de chave, escopos e o cabeçalho
Authorization. - Erros — todos os tipos de erro e o mapeamento de status HTTP.
- Transações — o contrato completo de criação de transação.
- Webhooks — reaja a mudanças de estado em vez de fazer polling.