Skip to content
Todos os artigos

Solicitações de verificação idempotentes: evitando verificações e cobranças duplicadas

Charles Archibong

, Cofundador

Atribua um requestId a cada verificação lógica, salve-o antes de enviar a solicitação e reutilize-o em cada nova tentativa. Um requestId repetido retorna a verificação existente em vez de criar e cobrar uma segunda.

Título "Tente novamente sem pagar duas vezes" ao lado de uma ilustração de arcos tracejados de nova tentativa convergindo para uma única marca de seleção, em um degradê azul-índigo profundo.

Pontos principais

  • Uma solicitação repetida de metadata.requestId retorna a verificação existente em vez de criar uma nova.
  • Gere o requestId a partir do seu próprio registro e armazene-o antes da primeira tentativa, não dentro do loop de repetição.
  • Tente novamente os códigos 429, 502, 503, 504 e falhas de conexão com um período de espera; trate outras respostas 4xx como bugs a serem corrigidos.
  • O código 202 significa apenas que o item está na fila. Uma falha na verificação é um resultado a ser tratado, não um erro que exija nova tentativa.

Para tentar novamente uma solicitação de verificação com segurança, envie o mesmo.metadata.requestIdtoda vez que você tentar novamente. Toden guloseimasrequestIdcomo sua chave de idempotência: se uma solicitação chegar com umrequestIdSua organização já utilizou o API Retorna a verificação existente em vez de criar uma segunda, e você não é cobrado duas vezes.

Esse único campo só te protege se você o utilizar corretamente.requestIdDeve ser gerado uma vez por verificação lógica, salvo antes da primeira tentativa e reutilizado integralmente. Gere-o dentro do seu loop de repetição e cada nova tentativa se tornará uma verificação completamente nova. O restante deste artigo aborda onde criá-lo, quais respostas devem ser repetidas e quais "falhas" não são falhas de fato.

O que dá errado sem idempotência?

O caso clássico é um tempo limite após uma operação bem-sucedida. Seu servidor enviaPOST /api/kyc/verify, o API O sistema aceita a solicitação e enfileira a verificação, mas a resposta se perde no caminho de volta: uma queda na conexão móvel, um tempo limite do balanceador de carga, uma reinicialização do pod. Do seu lado, a solicitação falhou. Do nosso, foi bem-sucedida.

Se o seu código tentar novamente com um novo identificador, a plataforma verá uma nova solicitação e criará uma segunda verificação. Em Produção, cada verificação concluída é debitada do seu saldo de crédito, de acordo com a documentação do ambiente (abre num novo separador) , portanto, a resposta perdida resultou em uma segunda verificação. Isso também deixa dois IDs de verificação vinculados a um mesmo solicitante, e sua equipe de reconciliação agora precisa determinar qual deles é o verdadeiro.

Com o mesmorequestId, a nova tentativa retorna a verificação original e a resposta perdida não tem custo.

Como funciona o requestId no endpoint de verificação?

Ele reside dentro da solicitação.metadataobjeto. Da referência de verificação de criação (abre num novo separador) :

{
  "country": "NG",
  "idType": "bvn",
  "idNumber": "12345678901",
  "externalUserId": "user_42",
  "metadata": { "requestId": "order_1001", "loanId": "loan_20191" }
}

O primeiro envio retorna202 Accepted:

{
  "verificationId": "ver_01j9...",
  "status": "processing",
  "externalUserId": "user_42",
  "metadata": { "loanId": "loan_20191" }
}

Uma submissão posterior com o mesmorequestIdretorna essa verificação, qualquer que seja o estado em que ela se encontre, por exemplo{ "verificationId": "ver_01j9...", "status": "approved" }.externalUserIde seus metadados retornam exatamente como foram armazenados, de modo que a nova tentativa e a original coincidem.

As chaves são definidas de acordo com o escopo da sua organização. Se umarequestIdcolide com uma pertencente a outra organização, a API retornos403com{ "error": "Forbidden" }em vez de divulgar qualquer informação sobre essa verificação.

Onde você deve gerar o requestId?

Com base no seu próprio histórico, e antes de enviar qualquer coisa. Um padrão que se confirma:

  1. Quando um cliente inicia a verificação de identidade, crie uma linha em seu próprio banco de dados para essa tentativa (uma solicitação, uma etapa de integração, um empréstimo).

  2. Derive orequestIddessa linha, por exemplokyc-application-8841e armazene-o na linha.

  3. Envie a solicitação com esse valor armazenado.

  4. Em qualquer nova tentativa, leia o valor da linha de volta. Nunca calcule um novo.

Seguem-se três regras:

  • Não use um número aleatório. UUID gerado por HTTP chamada. Esse é um identificador único para a chamada, o que é o oposto do que você precisa.

  • Defina um namespace. Um simples...1001É provável que entre em conflito com seus outros registros ao longo do tempo, e um conflito entre organizações retorna.403Um prefixo comokyc-application-torna o valor inequívoco.

  • Crie uma nova verificação apenas para uma verificação genuinamente nova. Se um cliente for recusado e você decidir que ele pode começar do zero, essa é uma nova verificação lógica com uma nova solicitação.requestIdReutilizar a versão antiga simplesmente retornaria a verificação recusada.

Se um avaliador enviar uma verificação para que o candidato possa refazer algumas etapas, você não precisa de uma nova.requestIdDe forma alguma. Uma verificação que é reenviada ou verificada novamente mantém seu ID, e o reenvio aparece como uma nova.attemptnele, conforme descrito na referência do resultado da verificação (abre num novo separador) .

Quando o SDK envia para você

A maioria das integrações não faz chamadas./verifydiretamente. Os SDKs da web, React Native e Flutter fazem a chamada quando o candidato clica em enviar. Eles aceitammetadatacom o seurequestId(metadata={{ requestId: "order_1001" }}Em React Native,metadata: {'requestId': 'order_1001'}Em Flutter, portanto, o mesmo princípio se aplica: passe um valor derivado do seu próprio registro, e não um gerado do zero cada vez que a tela é montada.

Quais respostas você deve tentar novamente?

A documentação sobre erros (abre num novo separador) é explícita e corresponde a uma tabela resumida.

Resposta

Tentar novamente?

O que fazer

202 Accepted

Não

Feito. Acompanhe overificationId.

429 Too Many Requests

Sim

Aguarde oRateLimit-Resetsegundos, depois tente novamente com o mesmorequestId.

502,503,504

Sim

Recuo exponencial com jitter, mesmorequestId.

Reinicialização da conexão, DNS falha, tempo limite

Sim

Igual ao anterior. A solicitação pode ter sido bem-sucedida, e é exatamente por isso que...requestIdassuntos.

500

Não cegamente

Guarde os detalhes da solicitação e entre em contato com o suporte se o problema persistir.pricing_not_configuredNão se corrigirá automaticamente ao tentar novamente.

400 Invalid request body

Não

Corrija a carga útil. Leiamessage.

401 Invalid API key

Não

Verifique a chave e seu ambiente.

403 business_not_approved

Não

A produção requer um perfil comercial aprovado.

403 Forbidden

Não

SeurequestIdentra em conflito com o de outra organização. Seu esquema de geração não é suficientemente exclusivo.

O limite documentado é de 100 solicitações a cada 15 minutos por cliente, comRateLimit-Limit,RateLimit-RemainingeRateLimit-ResetCabeçalhos nas respostas. LeituraRateLimit-RemainingPermite que uma tarefa em lote seja processada mais lentamente antes de ser recusada, em vez de depois. A página de limites de taxa (abre num novo separador) observa que os limites podem ser diferentes entre os ambientes de Sandbox e Produção e podem ser ajustados para sua conta.

Como seria um loop de repetição seguro?

Uma versão mínima em Node.js, usando o globalfetchO corpo é serializado uma única vez, fora do loop, portanto, cada tentativa envia bytes idênticos com o mesmorequestId.

const VERIFY_URL = 'https://api.toden.co/api/kyc/verify';
const RETRYABLE = new Set([429, 502, 503, 504]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const backoff = (attempt) => Math.min(30_000, 500 * 2 ** attempt) * (0.5 + Math.random());

export async function submitVerification(body, apiKey, maxAttempts = 5) {
  if (!body.metadata?.requestId) throw new Error('metadata.requestId is required');
  const payload = JSON.stringify(body);

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    let res;
    try {
      res = await fetch(VERIFY_URL, {
        method: 'POST',
        headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
        body: payload,
      });
    } catch (networkError) {
      if (attempt === maxAttempts) throw networkError;
      await sleep(backoff(attempt));
      continue;
    }

    if (res.ok) return res.json(); // 202 for a new verification, or the existing one on a replay

    if (!RETRYABLE.has(res.status) || attempt === maxAttempts) {
      const detail = await res.json().catch(() => ({}));
      throw Object.assign(new Error(detail.error ?? `HTTP ${res.status}`), { status: res.status, detail });
    }

    const resetSeconds = Number(res.headers.get('RateLimit-Reset'));
    await sleep(res.status === 429 && resetSeconds > 0 ? resetSeconds * 1000 : backoff(attempt));
  }
}

Observe o que está ausente: nenhuma nova tentativa em400,401ou403e nenhum novo identificador em lugar nenhum. O chamador fornece umrequestIdJá está armazenado.

Uma verificação falhada é algo que deve ser tentado novamente?

Não, e confundir os dois é uma fonte comum de duplicatas.202Isso significa apenas que a solicitação foi enfileirada. A verificação ainda pode ser concluída.declinedporque não houve correspondência facial ou porque o documento de identidade não foi encontrado no banco de dados do governo. Esses são resultados fornecidos por webhook ou por votação, cada um com umreasone um estávelreasonCodeEles não são. HTTP erros.

Se a sua lógica de repetição monitora os resultados e reenvia em caso de recusa, ela receberá a mesma verificação recusada de volta (a mesma).requestId) ou criar uma segunda verificação paga da mesma entrada incorreta (nova)requestIdNenhuma das duas ajuda. Ramifique-se noreasonCodeEm vez disso: peça uma nova foto emdocument_blurry, para um número corrigido emidentity_not_foundE, deliberadamente, submeter a pessoa a uma nova verificação ou a uma verificação refeita.

O lado do webhook também precisa de idempotência?

Sim. A mesma entrega de webhook pode chegar mais de uma vez, pois uma nova tentativa de entrega é feita até que seu endpoint a confirme. A documentação de webhooks (abre num novo separador) recomenda a remoção de duplicatas noX-Myaza-Deliverycabeçalho, ou emverificationIdRegistre o que você processou e defina o manipulador como uma operação nula na segunda vez. O envio e o recebimento idempotentes, juntos, são o que tornam o fluxo exatamente uma vez do ponto de vista do seu cliente.

Lista de verificação

  • UmrequestIdpor verificação lógica, derivada do seu próprio registro e com namespace.

  • Armazenado antes da primeira tentativa; lido novamente, nunca regenerado, em uma nova tentativa.

  • Passou pelo SDK 'smetadataquando o SDK submete em seu nome.

  • Tentativas de relançamento429,502,503,504e apenas falhas de rede, com recuo e oRateLimit-Resetvalor.

  • Resultados comodeclinedtratado porreasonCode, nunca por meio de um novo envio.

  • O manipulador de webhook foi desduplicado emX-Myaza-DeliveryouverificationId.

O contrato de solicitação completo está na referência de verificação de criação (abre num novo separador) , e o hub do desenvolvedor contém os links para o restante. API .

Fontes

Charles Archibong

Sobre o autor

Charles Archibong

Cofundador

Charles Archibong é cofundador da Toden. Ele escreve sobre verificação de identidade, tecnologia financeira e o trabalho prático de construção de serviços digitais confiáveis.

  • Desenvolvedores

    Verificar corretamente as assinaturas do webhook e lidar com novas tentativas.

    Verificar a assinatura V2 nos bytes brutos da solicitação, rejeitar carimbos de data/hora fora da janela de reprodução, registrar o ID do evento sob uma restrição exclusiva antes de realizar qualquer operação e retornar 2xx somente quando o evento for armazenado de forma duradoura.

  • Desenvolvedores

    Mapeamento dos status de verificação para os estados do seu produto.

    Gere o status do seu produto a partir do resultado e armazene `checkStatus` ao lado como explicação. Mapeie cada um dos dez valores de status para um estado em seu próprio sistema e obtenha o status mais recente de `verification.status_updated` ou por meio de polling.

Crie o seu produto.Nós tratamos do resto.

Identidade e conformidade de ponta a ponta, segundo padrões globais, com preços para fundadores.