Menu da documentação
Guias
Idempotência
Rede cai, timeout estoura, processo reinicia. A Idempotency-Key deixa você repetir um POST sem cobrar nem cancelar duas vezes. Referência: referência da API.
A regra em uma frase
Uma chave por operação lógica, gravada antes de enviar, reaproveitada em toda repetição da mesma operação.
- "Operação lógica" é "cobrar a guia 778899 do contribuinte X", não "a requisição HTTP número 3".
- Ela é obrigatória em todo
POSTsob/v1(criar e cancelar). Sem ela:400 idempotency_key_required. - Até 255 caracteres. Use um UUID.
- Vale 24 h e é do seu tenant: a mesma chave em outro tenant é outra requisição.
O que o Pagaki faz com a chave
| Você envia | O Pagaki responde |
|---|---|
| Chave nova | Executa e guarda a resposta |
| Mesma chave, mesma rota, mesmo corpo, até 24 h | A resposta original, idêntica, sem executar de novo, com Idempotent-Replayed: true |
| Mesma chave com outro corpo ou em outra rota | 422 idempotency_key_reused |
| Mesma chave enquanto a primeira ainda roda | 409 idempotency_key_in_progress |
O corpo é comparado como JSON normalizado: ordem das chaves e espaços não contam.
Quando repetir com a mesma chave, quando usar outra
| O que aconteceu | Mesma chave? |
|---|---|
| Timeout, conexão caiu, sem resposta | Sim: se a primeira chegou a executar, você recebe a resposta dela |
500 internal_error |
Sim: o 500 não fica guardado |
409 idempotency_key_in_progress |
Sim, depois de alguns segundos |
429 rate_limited |
Sim, depois do Retry-After: a requisição barrada não reservou a chave |
422 validation_failed |
Pode: corrija o corpo e reenvie; a validação não fica guardada |
422 no_gateway_available, gateway_invalid_data, cancel_not_supported, 409 invalid_state |
Não: a resposta fica guardada. Corrija a causa e use uma chave nova |
502 gateway_rejected ou gateway_failure |
Não: o 502 fica guardado. Na criação, o pagamento já está failed; crie outro com chave nova. No cancelamento, repita com chave nova |
201/202/200 |
Não há o que repetir. Se repetir, recebe a mesma resposta |
Exemplo: criar com repetição segura
KEY=$(uuidgen) # guarde KEY junto da guia antes de chamar
for i in 1 2 3; do
curl -s --max-time 15 https://api.sandbox.pagaki.com.br/v1/payments \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d '{"method":"pix","amount":12550,"external_id":"guia-778899","payer":{"name":"Fulano de Tal","document":"12345678909"}}' \
&& break
sleep $((i * 2))
done
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
/** $key vem do seu banco: gravado junto do pedido antes da primeira tentativa. */
function createPix(Client $http, string $token, string $key, array $body): array
{
for ($attempt = 1; ; $attempt++) {
try {
$response = $http->post('/v1/payments', [
'headers' => ['Authorization' => "Bearer {$token}", 'Idempotency-Key' => $key],
'json' => $body,
'timeout' => 15,
'http_errors' => false,
]);
} catch (ConnectException $e) {
if ($attempt === 3) {
throw $e;
}
sleep($attempt * 2);
continue; // mesma chave: se a primeira executou, vem a resposta dela
}
$status = $response->getStatusCode();
if (in_array($status, [409, 429, 500], true) && $attempt < 3) {
sleep((int) ($response->getHeaderLine('Retry-After') ?: $attempt * 2));
continue;
}
return ['status' => $status, 'body' => json_decode((string) $response->getBody(), true)];
}
}
/** key vem do seu banco: gravado junto do pedido antes da primeira tentativa. */
export async function createPix(token, key, body) {
for (let attempt = 1; ; attempt++) {
let response;
try {
response = await fetch('https://api.sandbox.pagaki.com.br/v1/payments', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'Idempotency-Key': key },
body: JSON.stringify(body),
signal: AbortSignal.timeout(15_000),
});
} catch (error) {
if (attempt === 3) throw error;
await new Promise((r) => setTimeout(r, attempt * 2000));
continue; // mesma chave: se a primeira executou, vem a resposta dela
}
if ([409, 429, 500].includes(response.status) && attempt < 3) {
const wait = Number(response.headers.get('Retry-After')) || attempt * 2;
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
return { status: response.status, body: await response.json() };
}
}
Erros comuns
- Gerar a chave dentro do laço de repetição. Cada tentativa vira um pagamento novo. Gere uma vez, fora do laço, e grave.
- Reaproveitar a chave da criação no cancelamento. Dá
422 idempotency_key_reused: são operações diferentes. - Usar o
external_idcomo chave. Funciona só se você nunca cobrar a mesma guia duas vezes (uma segunda via, por exemplo, precisa de chave nova). Prefira um UUID por tentativa de cobrança e guarde oexternal_idà parte. - Contar com a chave depois de 24 h. Passado esse prazo, a mesma chave é uma requisição nova.
Webhooks
A idempotência de quem recebe é outra: o mesmo evento pode chegar mais de uma vez, e você deduplica pelo id do evento (evt_…). Ver webhooks.