Webhooks: eventos, assinaturas e entrega
Registre um endpoint HTTPS público, prove controle e receba eventos assinados de fatura e pagamento. A entrega é enfileirada de forma durável; processar o mesmo evento novamente deve ser seguro para seu pedido.
Nesta página
De onde vêm os eventos#
invoice.created não significa pago. Eventos de pagamento vêm de uma operação de servidor verificada após uma leitura atual do gateway; um nome de evento recebido sozinho é insuficiente.
Entrada Gateway → Kevmi e entrega Kevmi → comerciante são pernas diferentes com segredos separados. O navegador, a navegação de retorno e mensagens de widget não definem estado financeiro.
| Evento | Fonte |
|---|---|
| invoice.created | Fatura persistida |
| invoice.updated | Disponibilidade do link alterada |
| payment.succeeded | Pagamento exato verificado pelo servidor |
| payment.failed | Operação falha verificada |
| payment.refunded | Reembolso total verificado |
| payment.partially_refunded | Reembolso parcial verificado |
| payment.disputed | Disputa verificada |
| payment.review_required | Operação requer revisão adicional |
Registrar e verificar um endpoint#
URLs não podem apontar para endereços IP, localhost, redes privadas, portas não padrão ou conter credenciais. DNS é verificado antes da entrega; solicitações POST de saída não seguem redirecionamentos. Uma conta pode registrar até cinco endpoints.
O evento de verificação tem livemode=false. Um status HTTP de sucesso sozinho é insuficiente: o desafio deve coincidir exatamente. Girar o segredo exige nova verificação.
- 1
Em /account#webhooks na sua conta de comerciante, insira uma URL HTTPS pública, uma descrição e os tipos de evento necessários.
- 2
Salve o segredo emitido no armazenamento do lado do servidor. Seu valor completo é mostrado uma única vez.
- 3
Verifique assinaturas sobre os bytes originais da solicitação.
- 4
Execute a verificação do endpoint. Para endpoint.verification, retorne JSON ecoando data.challenge: {"challenge":"o valor recebido"}.
- 5
Ative o endpoint verificado e inspecione o histórico de entrega.
Envelope de evento#
id permanece o mesmo em cada tentativa de entrega de um evento. createdAt é um timestamp Unix em milissegundos, e apiVersion identifica a versão do contrato. data contém o objeto de servidor persistido; o exemplo foi encurtado.
livemode=true distingue um evento de servidor real de uma verificação de endpoint. Isso não significa que uma fatura foi paga: verifique o tipo e os dados de pagamento. Trate tipos de evento conhecidos e valide o esquema.
{
"id": "evt_example",
"type": "invoice.created",
"apiVersion": "2026-10-21",
"createdAt": 1792540800000,
"livemode": true,
"data": {
"invoice": {
"id": "lnk_example",
"orderId": "order-1042",
"currency": "USD",
"amountMinor": 500,
"vatCustomerPercent": 100,
"feeCustomerPercent": 0
}
}
}Verifique HMAC-SHA256#
Kevmi-Signature contém t=segundos,v1=hex. A assinatura é HMAC-SHA256 sobre a string de timestamp, um ponto e o corpo original: timestamp + "." + rawBody. Use o segredo específico do endpoint e uma comparação em tempo constante.
Rejeite assinaturas cujo timestamp difira do horário do servidor por mais de 300 segundos em qualquer direção. Sincronize relógios. Não analise e reserialize JSON antes da verificação: espaços em branco e ordem de propriedades afetam os bytes assinados.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyKevmi(rawBody, header, secret, nowSeconds) {
const match = /^t=(\d{10}),v1=([a-f0-9]{64})$/.exec(header || '');
if (!match || Math.abs(nowSeconds - Number(match[1])) > 300) {
return false;
}
const expected = createHmac('sha256', secret)
.update(match[1] + '.')
.update(rawBody)
.digest();
const received = Buffer.from(match[2], 'hex');
return expected.length === received.length
&& timingSafeEqual(expected, received);
}
// Read bytes before JSON parsing; cap the request body.
// secret comes from your server secret store.
const valid = verifyKevmi(rawBody, signature, secret,
Math.floor(Date.now() / 1000));
if (!valid) throw new Error('Invalid webhook signature');
const event = JSON.parse(rawBody.toString('utf8'));
// Validate the event schema and persist its ID together
// with your order update in one database transaction.
// Return a 2xx response after durable acceptance.Tentativas e reconhecimento#
Qualquer resposta 2xx reconhece um evento comum. Persista o evento de forma durável antes de responder; trabalho caro pode continuar através da sua própria fila. O timeout de resposta é de oito segundos. Um erro de rede ou resposta não-2xx agenda uma nova tentativa.
Há no máximo seis tentativas: a entrega inicial mais atrasos de um minuto, cinco minutos, 30 minutos, duas horas e 12 horas após falhas sucessivas. Estes são agendamentos de fila, não garantias de horário exato de entrega. Esgotar as tentativas marca a entrega como falha.
A entrega é pelo menos uma vez e não promete ordenação estrita. Persista o ID único do evento na mesma transação da sua atualização de pedido. Um duplicado já aplicado deve receber 2xx sem atender o pedido novamente.
Uma nova tentativa manual reinicia as tentativas de entrega preservando o ID original do evento, o ID de entrega e o corpo exato. Cada tentativa recebe um timestamp de assinatura novo. Pausar um endpoint preserva sua fila; excluí-lo cancela entregas pendentes e com falha.
Histórico de entrega e reconciliação#
A interface mostra o estado do endpoint, entregas recentes, contagens de tentativas, última resposta HTTP, erro e próximo horário de tentativa. Estes diagnósticos complementam seus próprios registros de pedidos.
GET /api/v1/events com events:read retorna até 50 eventos e nextCursor. Passe o nextCursor opaco inalterado como cursor; null marca o fim. A ordenação usa tanto createdAt quanto id. O before legado no limite de milissegundos Unix permanece disponível separadamente e não pode ser combinado com cursor. Endpoints verificados recebem eventos assinados no máximo a partir do registro; endpoints pausados continuam acumulando eventos enfileirados para entrega após retomar. Isso não promete histórico anterior à criação do endpoint.
Verificações de recebimento do gateway de entrada#
Gateway v3 envia para /api/gateway/webhook com X-Gateway-Event-Id, X-Gateway-Timestamp e X-Gateway-Signature: v1=<hex>. HMAC-SHA256 assina timestamp + "." + rawBody com GATEWAY_WEBHOOK_SECRET; a janela de relógio é de 300 segundos. Igualdade de ID cabeçalho/corpo, versão de API, modo ao vivo e hash do corpo são verificados.
Um ID de evento reutilizado com bytes de corpo diferentes é rejeitado. Uma notificação assinada aciona um GET autenticado da operação exata. Ordem, método, moeda, base, total aprovado e versão são verificados. Sucesso exige fulfillment_ready=true, verification=provider_confirmed, o valor pago exato e nenhuma disputa; cripto também precisa de evidência de liquidação confirmada.
Tratamento de entrada, recibos e eventos de comerciante são protegidos contra duplicados. Formatos antigos de notificação apenas para navegador não podem estabelecer pagamento bem-sucedido. Webhooks de comerciante nunca contêm campos de cartão, códigos bancários, dados de QR, credenciais do provedor ou o segredo do gateway.
Telegram tem um contrato separado#
POST /api/telegram/webhook verifica X-Telegram-Bot-Api-Secret-Token, a estrutura do update e o remetente. update_id protege contra processamento duplicado. Esta rota trata login, comandos, suporte e ações de moderação; ela não aceita recibos financeiros.
O proprietário da implantação configura o token do bot, o segredo do webhook, o registro setWebhook e o endereço HTTPS acessível. Eles são separados do segredo de assinatura do endpoint do comerciante.
Múltiplas lojas e um preço base em USD#
Escolha Todas as lojas para análises e operações agregadas, ou selecione uma loja para seus próprios registros. Cada loja tem um storeId separado, nome, padrão de IVA, faturas, chaves de API, endpoints de webhook, histórico de eventos e aplicativo de site. Notificações e tickets de suporte pertencem à conta. Uma conta pode criar até 100 lojas. Registros existentes permanecem na primeira loja.
As faturas do novo workspace usam pricingMode="universal" e um preço base em USD. O plano Any inclui cartão, alipay, sbp e cripto; um comerciante pode selecionar um subconjunto. Cartões e Alipay cobram em USD. O SBP cobra em RUB usando a taxa de referência USD/RUB do Banco da Rússia. Cripto cobra USDT sob a regra explícita de fatura 1 USD = 1 USDT. Esta é uma regra de conversão de fatura, não uma cotação de mercado de USDT. O gateway fornece a taxa da moeda selecionada, o valor exato da transferência e instruções antes da transferência.
A taxa de referência e ambos os valores são congelados na criação. Faturas que envolvem conversão RUB expiram dentro de 24 horas. Se a taxa de referência não puder ser obtida, a criação falha com FX_UNAVAILABLE; nenhuma taxa de fallback inventada ou desatualizada é usada. O valor do proprietário em minor e cotações usam a moeda contábil USD. quotes[method].settlement contém a cotação de cobrança nativa. O checkout público expõe a moeda de cobrança selecionada em currency, o preço original em baseAmountMinor/baseCurrency, e a taxa de referência em fx. Uma vez que um pagamento foi enviado, seu método não pode ser alterado; uma fatura tem uma operação de gateway.
Crie uma chave de API dentro da loja pretendida e envie Authorization: Bearer <key> do seu servidor. Seu vínculo de loja é imutável; X-Kevmi-Store não pode conceder acesso a outra loja. Sessões de navegador selecionam X-Kevmi-Store: <storeId>; omitir seleciona a primeira loja. O valor all é aceito apenas por GET /api/account/dashboard e GET /api/payment-links. Mutações exigem uma loja selecionada, Origin e X-CSRF-Token. IDs de conta e IDs de loja são identificadores diferentes.
Para compatibilidade, solicitações de API sem pricingMode="universal" mantêm preço nativo e as restrições originais de moeda. Faturas existentes nunca são recalculadas. Recibos confirmados nas análises refletem pagamentos verificados menos despesas de fatura salvas; eles não são um saldo sacável. Moedas de fatura legadas permanecem separadas e pagamentos são combinados com o suporte. Cumpra um pedido somente após verificar payment.fulfillmentReady=true e payment.status="succeeded" através da API do servidor, histórico de eventos ou um webhook assinado.