1. Prepare o backend e uma chave de teste
Comece com uma chave cp_test_ criada no portal, com as permissões pix:write e transactions:read. Guarde o segredo em CALEBE_API_KEY no servidor. O navegador ou aplicativo conversa com o seu backend; a chave da API não deve aparecer no bundle, no localStorage ou em variáveis públicas.
A chave escolhe o ambiente. Com cp_test_, a mesma rota POST /v1/pix gera uma transação simulada, sem cobrança real e sem envio ao provedor. A resposta deve trazer livemode: false, simulated: true e environment: sandbox. O código retornado nesse modo não é pagável.
2. Salve o pedido antes da chamada
No banco da sua aplicação, grave o pedido, uma chave de idempotência exclusiva para a tentativa e o payload exato da cobrança. Isso deve acontecer antes da rede: se a conexão cair, sua aplicação precisa conseguir recuperar a tentativa original.
O valor é um inteiro em centavos: amount: 1250 representa R$ 12,50. Envie reference, description e o customer com nome, documento, e-mail, telefone e endereço completo. O endereço inclui CEP, logradouro, número, bairro, cidade e UF. Para Pix, expires_in define a validade em segundos, entre 60 e 86400; o padrão é 3600.
Use os dados legítimos do pagador em produção. O JSON de exemplo da documentação contém dados fictícios para simulação; ele não deve preencher campos ausentes de um cliente real.
- Idempotency-Key aceita de 1 a 128 caracteres: letras ASCII, números, ponto, hífen, dois-pontos e sublinhado.
- Uma referência como pedido-42 ajuda a localizar a venda, mas não substitui a chave de idempotência e não é necessariamente única.
- A conta de emissão vem da API key. Não envie tenant_id, provider_id ou instruções de split no corpo público.
3. Envie o pedido salvo com fetch
Esta função deve rodar somente no backend. Ela recebe a chave de idempotência e o payload que sua aplicação já persistiu. O exemplo exige uma chave de teste deliberadamente, usa um limite de espera de 30 segundos e recusa redirecionamentos com credenciais.
Depois de receber a resposta, salve transaction.id no mesmo pedido antes de apresentar a cobrança ao usuário. O exemplo não cria chaves novas nem repete a chamada automaticamente. Ele também não interpreta uma resposta HTTP de sucesso como pagamento confirmado.
const apiKey = process.env.CALEBE_API_KEY;
if (!apiKey?.startsWith('cp_test_')) {
throw new Error('Use uma chave de teste neste exemplo.');
}
// payload e idempotencyKey já estão salvos no pedido.
export async function emitirPix({ payload, idempotencyKey }) {
if (!payload || !idempotencyKey) {
throw new Error('Recupere os dados persistidos do pedido.');
}
const response = await fetch('https://api.calebepay.com.br/v1/pix', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey,
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(30_000),
redirect: 'error',
});
const result = await response.json();
if (!response.ok) {
const error = new Error(result.error?.code ?? 'api_error');
error.transactionId = result.error?.transaction_id;
throw error;
}
return { httpStatus: response.status, transaction: result.data };
}4. Diferencie emissão, incerteza e pagamento
Uma nova emissão conhecida retorna HTTP 201. Um replay pode retornar 200 com Idempotency-Replayed: true. Uma resposta 202 indica processamento ou resultado incerto. Em todos esses casos, leia o status da transação; nenhum desses códigos HTTP, sozinho, confirma pagamento.
Os campos pix_copy_paste e pix_qr_code podem ser nulos em uma resposta parcial. Espere um instrumento disponível e válido antes de mostrá-lo. O document_url é o endereço da cobrança que pode ser apresentado ao pagador; trate esse link como um documento que contém dados da cobrança.
Se ocorrer timeout ou falha de conexão, mantenha o payload e a chave originais. Consulte GET /v1/transactions/{id} quando conhecer o ID ou recupere o registro repetindo o mesmo POST. Não gere uma cobrança nova só porque a resposta não chegou. Uma tentativa failed também continua vinculada à chave anterior.
| Situação | Tratamento no sistema |
|---|---|
| pending / processing | Acompanhar a tentativa; ainda não liberar o pedido. |
| unknown ou timeout | Reconciliar o resultado existente, sem trocar automaticamente a chave. |
| 409 idempotency_conflict | Recuperar método, corpo e chave originais da tentativa. |
| paid em teste | Validar a integração; não representa dinheiro recebido. |
5. Confirme o Pix pelo webhook e pela conciliação
Cadastre no portal um endpoint de teste para receber transaction.updated. Antes de confiar no evento, valide Calebe-Signature com HMAC-SHA256 sobre o timestamp e os bytes originais do corpo, usando o segredo de webhook da conta. Confira a tolerância de horário e compare a assinatura em tempo constante.
Persista o evento antes de responder 2xx e use seu id para reconhecer repetições. Entregas podem chegar novamente ou fora de ordem. Para consultar o estado atual, use GET /v1/transactions/{id}; mantenha uma rotina limitada de conciliação no backend e use POST /v1/transactions/{id}/refresh quando precisar consultar o provedor.
No teste, POST /v1/transactions/{id}/simulate com status paid ou expired permite validar a mudança de uma cobrança pendente e seu webhook. A liberação de uma venda real exige status paid junto de livemode: true, environment: production e simulated: false, conferidos pelo backend.
6. Passe para produção com a conta habilitada
A URL e o contrato da API permanecem iguais. A passagem à produção exige liberação da conta, chave de produção no servidor e endpoints de webhook no modo correto. A restrição cp_test_ do exemplo precisa ser revisada conscientemente na implementação de produção.
Mantenha os registros de teste separados dos pedidos reais, confirme o ambiente de cada resposta e acompanhe eventuais recusas no monitor de integração. Uma resposta live_payments_disabled indica que a produção ainda não está liberada; trocar a chave de idempotência não resolve essa autorização.