Guia de Idempotência
Quick Reference
No processamento de pagamentos, uma retentativa não idempotente é um erro financeiro:
- Seu servidor envia uma requisição de cobrança
- A cobrança é realizada com sucesso, mas a resposta é perdida devido a um timeout de rede
- Seu servidor retenta — sem uma chave de idempotência, a A55 cria uma segunda cobrança
- O cliente é cobrado duas vezes
Chaves de idempotência resolvem isso: se a A55 receber uma chave duplicada, ela retorna a resposta original em vez de criar uma nova cobrança. Isso torna as retentativas seguras — o cliente é cobrado exatamente uma vez, independentemente de quantas vezes você enviar a requisição.
Como Funciona
Envie um header Idempotency-Key em toda requisição POST:
POST /api/v1/bank/wallet/charge/ HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
A A55 armazena a chave e sua resposta associada. Se a mesma chave for enviada novamente dentro da janela de expiração:
- Mesmo payload → Retorna a resposta original (HTTP 200/201)
- Payload diferente → Retorna HTTP
409 Conflict
Formato da Chave
Use UUID v4 para chaves de idempotência:
550e8400-e29b-41d4-a716-446655440000
Requisitos:
- Única por operação de negócio (não por tentativa de retry)
- Gerada uma única vez e reutilizada em todas as tentativas de retry da mesma operação
- Mínimo de 16 caracteres, máximo de 128 caracteres
Endpoints Suportados
Todos os endpoints POST suportam idempotência:
| Endpoint | Caso de Uso |
|---|---|
POST /api/v1/bank/wallet/ | Criação de wallet |
POST /api/v1/bank/wallet/charge/ | Criação de cobrança |
POST /api/v1/bank/wallet/charge/refund/ | Reembolso |
POST /api/v1/bank/wallet/charge/capture/ | Captura |
POST /api/v1/bank/wallet/charge/cancel/ | Cancelamento |
Requisições GET, PUT e DELETE são naturalmente idempotentes e não necessitam do header.
Comportamento de Requisições Duplicadas
| Cenário | Resultado |
|---|---|
| Mesma chave + mesmo payload | Retorna a resposta original |
| Mesma chave + payload diferente | HTTP 409 Conflict |
| Mesma chave após expiração (24h) | Tratada como nova requisição |
| Sem chave fornecida | Cada requisição cria um novo recurso |
Expiração da Chave
As chaves de idempotência expiram após 24 horas. Após a expiração, a mesma chave pode ser reutilizada para uma nova requisição. Projete sua estratégia de retry para concluir bem antes dessa janela.
Exemplos de Código
- cURL
- Python
- Node.js
# Generate an idempotency key ONCE for this operation
IDEMPOTENCY_KEY=$(uuidgen)
# First attempt
curl -s -X POST "https://sandbox.api.a55.tech/api/v1/bank/wallet/charge/" \
-H "Authorization: Bearer ${A55_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"amount": "100.00",
"currency": "BRL",
"type_charge": "credit_card",
"card": { "number": "4111111111111111", "holder_name": "TEST", "expiration_month": "12", "expiration_year": "2030", "cvv": "123" },
"payer": { "name": "Test", "email": "test@example.com", "document": "12345678909", "document_type": "CPF" }
}'
# Retry with SAME key — returns the same response, no duplicate charge
curl -s -X POST "https://sandbox.api.a55.tech/api/v1/bank/wallet/charge/" \
-H "Authorization: Bearer ${A55_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"amount": "100.00",
"currency": "BRL",
"type_charge": "credit_card",
"card": { "number": "4111111111111111", "holder_name": "TEST", "expiration_month": "12", "expiration_year": "2030", "cvv": "123" },
"payer": { "name": "Test", "email": "test@example.com", "document": "12345678909", "document_type": "CPF" }
}'
import os
import uuid
import time
import random
import requests
def create_charge_idempotent(charge_data: dict, max_retries: int = 3):
idempotency_key = str(uuid.uuid4())
headers = {
"Authorization": f"Bearer {os.environ['A55_ACCESS_TOKEN']}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
}
for attempt in range(max_retries + 1):
try:
response = requests.post(
"https://sandbox.api.a55.tech/api/v1/bank/wallet/charge/",
headers=headers,
json=charge_data,
)
if response.status_code == 409:
raise ValueError(f"Idempotency conflict: {response.json()}")
if response.status_code < 500:
return response.json()
delay = (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
except requests.ConnectionError:
if attempt == max_retries:
raise
time.sleep(2 ** attempt)
raise Exception("Max retries exceeded")
charge = create_charge_idempotent({
"amount": "100.00",
"currency": "BRL",
"type_charge": "credit_card",
"card": {
"number": "4111111111111111",
"holder_name": "TEST",
"expiration_month": "12",
"expiration_year": "2030",
"cvv": "123",
},
"payer": {
"name": "Test",
"email": "test@example.com",
"document": "12345678909",
"document_type": "CPF",
},
})
const crypto = require("crypto");
async function createChargeIdempotent(chargeData, maxRetries = 3) {
const idempotencyKey = crypto.randomUUID();
const headers = {
Authorization: `Bearer ${process.env.A55_ACCESS_TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
};
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const res = await fetch(
"https://sandbox.api.a55.tech/api/v1/bank/wallet/charge/",
{ method: "POST", headers, body: JSON.stringify(chargeData) }
);
if (res.status === 409) {
throw new Error(`Idempotency conflict: ${await res.text()}`);
}
if (res.status < 500) {
return await res.json();
}
const delay = (2 ** attempt + Math.random()) * 1000;
await new Promise(r => setTimeout(r, delay));
} catch (err) {
if (err.message.includes("Idempotency")) throw err;
if (attempt === maxRetries) throw err;
await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
}
}
throw new Error("Max retries exceeded");
}
createChargeIdempotent({
amount: "100.00",
currency: "BRL",
type_charge: "credit_card",
card: {
number: "4111111111111111",
holder_name: "TEST",
expiration_month: "12",
expiration_year: "2030",
cvv: "123",
},
payer: {
name: "Test",
email: "test@example.com",
document: "12345678909",
document_type: "CPF",
},
});
Nunca gere uma nova chave por retentativa
A chave de idempotência deve ser gerada uma única vez por operação de negócio. Se você gerar uma nova chave para cada tentativa de retry, você perde completamente a proteção contra duplicatas.