Inovacc Desenvolvedores

Guias

Webhooks

Um webhook é a forma como Eventos entrega a um servidor que você opera: todo evento que corresponde a uma assinatura chega ao seu endpoint HTTPS como um POST assinado. Este guia cobre a criação de uma assinatura de webhook, a requisição que o seu endpoint recebe, a verificação da assinatura, como funcionam as novas tentativas e as mensagens mortas, e a rotação do segredo de assinatura sem perder nenhuma entrega.

O que é uma assinatura de webhook

Uma assinatura diz quais eventos um receptor quer e como eles chegam até ele. Uma assinatura de webhook tem uma lista de padrões de tipo e um bloco de entrega com "mode": "webhook" e a sua url. A partir daí, cada evento publicado no espaço de trabalho cujo type corresponda a um dos padrões é enviado por POST a essa URL, uma vez por assinatura.

A entrega é de pelo menos uma vez. Em operação normal cada evento chega uma vez; uma nova tentativa, uma resposta lenta ou uma conexão perdida pode fazê-lo chegar de novo. O seu receptor deve, portanto, ser construído para aceitar uma duplicata sem consequências, usando o id do evento como chave.

A URL deve ser https, na porta 443, sem nome de usuário ou senha, sem fragmento e com um host público: nomes privados, de loopback, de link local e de uso local são recusados quando a assinatura é criada e verificados de novo a cada entrega. Redirecionamentos nunca são seguidos.

Crie uma

Chame POST https://events.inovacc.dev/v1/workspaces/{workspace}/subscriptions com uma chave secreta que tenha a permissão de escrita de assinaturas:

Linguagem

⋮
    JSONExemplo

    JSON

    {"types": ["order.*"], "delivery": {"mode": "webhook", "url": "https://hooks.example.com/inovacc"}}

    A resposta é 201:

    Linguagem

    ⋮
      JSONExemplo

      JSON

      {"subscription_id": "sub_...", "types": ["order.*"],
       "delivery": {"mode": "webhook", "url": "https://hooks.example.com/inovacc", "secret_ref": "managed"},
       "created_at": "2026-10-06T12:00:00.000Z",
       "signing_secret": "whsec_..."}

      signing_secret é a chave que o seu endpoint usa para verificar as entregas. Ele é mostrado uma única vez, nesta resposta, e nunca mais: as listagens mostram "secret_ref": "managed" e nada mais. Guarde-o imediatamente no seu gerenciador de segredos. Se for perdido, rotacione-o (abaixo). Um espaço de trabalho comporta no máximo 50 assinaturas, cada uma com 1 a 20 padrões.

      A requisição de entrega

      Cada entrega é:

      POST https://hooks.example.com/inovacc
      content-type: application/json
      x-event-id: evt_01K...
      x-event-type: order.paid
      x-event-timestamp: 1791201600
      x-event-signature: v1=<64 lowercase hex characters>
      
      {"event_id":"evt_01K...","type":"order.paid", ... }
      

      x-event-timestamp é o horário Unix, em segundos, em que a entrega foi assinada. Durante uma rotação de segredo, x-event-signature traz uma entrada por segredo ativo, a mais nova primeiro, separadas por vírgulas: v1=<new>,v1=<old>.

      Responda com qualquer status 2xx para confirmar a entrega. Qualquer outra coisa (um 4xx, um 5xx, nenhuma resposta em 10 segundos, um erro de rede) é uma tentativa que falhou e será repetida.

      O envelope do evento

      O corpo é o envelope do evento, exatamente como os assinantes de todos os modos o recebem:

      CampoSignificado
      event_idevt_ seguido de 26 caracteres, atribuído pela Inovacc quando o evento foi publicado. Único por evento.
      typeO tipo do evento, como order.paid: palavras minúsculas separadas por pontos, até 128 bytes.
      sourceA aplicação que publicou o evento, obtida da sua credencial.
      timeQuando foi publicado, RFC 3339 UTC com milissegundos.
      tenantA organização, a conta, o espaço de trabalho e, quando conhecida, a aplicação a que o evento pertence.
      idempotency_keyA chave do publicador; quando ele não enviou nenhuma, o event_id.
      dataA carga JSON do publicador: o que aconteceu, normalmente ids em vez de objetos inteiros.

      Leia apenas os campos de que precisa e ignore os campos que você não conhece.

      Verifique a assinatura

      Verifique cada entrega antes de interpretá-la ou agir sobre ela:

      1. Leia x-event-timestamp e os bytes brutos do corpo, exatamente como recebidos, antes de qualquer interpretação de JSON.
      2. Calcule HMAC-SHA256(secret, timestamp + "." + body), em que secret é a string whsec_... inteira em UTF-8, e escreva o resultado em hexadecimal minúsculo.
      3. Divida x-event-signature pelas vírgulas e aceite a entrega quando qualquer entrada v1= for igual ao seu valor, comparando em tempo constante.
      4. Rejeite um carimbo de data e hora a mais de cinco minutos do seu relógio, para que uma entrega antiga não possa ser reenviada contra você.

      Exemplo prático. Com o segredo example-key-0001, o carimbo 1791201600 e o corpo {"hello":"world"}, o texto assinado é 1791201600.{"hello":"world"} e a assinatura são os 64 caracteres hexadecimais abaixo, impressos aqui em duas metades que você junta sem espaço:

      05080ab29a6abfb44033966c7837c2a1
      889fbf1a909f9cbd12b9dd5d12bb2bf5
      

      de modo que o cabeçalho fica v1= seguido das duas metades. Execute o seu verificador neste exemplo antes de implantá-lo.

      TypeScript (Node.js 18 ou posterior):

      Linguagem

      ⋮
        TypeScriptExemplo

        TypeScript

        import { createHmac, timingSafeEqual } from "node:crypto";
        
        export function verifyInovaccWebhook(
          secret: string,
          timestamp: string,
          signatureHeader: string,
          rawBody: Buffer,
          nowSeconds: number = Math.floor(Date.now() / 1000),
        ): boolean {
          const ts = Number(timestamp);
          if (!Number.isInteger(ts) || Math.abs(nowSeconds - ts) > 300) return false;
          const expected = createHmac("sha256", secret)
            .update(`${timestamp}.`)
            .update(rawBody)
            .digest();
          return signatureHeader.split(",").some((entry) => {
            const part = entry.trim();
            if (!part.startsWith("v1=")) return false;
            const given = Buffer.from(part.slice(3), "hex");
            return given.length === expected.length && timingSafeEqual(given, expected);
          });
        }

        Python (3.10 ou posterior):

        Linguagem

        ⋮
          PythonExemplo

          Python

          import hashlib
          import hmac
          import time
          
          
          def verify_inovacc_webhook(secret: str, timestamp: str, signature_header: str,
                                     raw_body: bytes, now: int | None = None) -> bool:
              try:
                  ts = int(timestamp)
              except ValueError:
                  return False
              now = int(time.time()) if now is None else now
              if abs(now - ts) > 300:
                  return False
              expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body,
                                  hashlib.sha256).hexdigest()
              return any(
                  part.strip().startswith("v1=")
                  and hmac.compare_digest(part.strip()[3:], expected)
                  for part in signature_header.split(",")
              )

          Ambas as funções recebem o corpo bruto: configure o seu framework web para entregar os bytes, e não um objeto interpretado, nesta rota.

          Novas tentativas e tempos limite

          O seu endpoint tem 10 segundos para responder. Uma tentativa que falhou é repetida após 10 segundos, e o atraso dobra a cada nova falha até 10 minutos. Quando o limite de 5 tentativas é atingido, o evento vai para as mensagens mortas do seu espaço de trabalho. Uma falha afeta apenas a entrega que falhou: os outros eventos, e as outras assinaturas do mesmo evento, continuam fluindo.

          Como uma entrega pode ser repetida, um 2xx que chega depois de o seu endpoint já ter processado o evento é inofensivo, desde que você deduplique por x-event-id.

          Rotacione o segredo

          POST /v1/workspaces/{workspace}/subscriptions/{id}/rotate-secret devolve um novo segredo, uma única vez:

          Linguagem

          ⋮
            JSONExemplo

            JSON

            {"subscription_id": "sub_...", "signing_secret": "whsec_...", "previous_valid_until": "2026-10-07T12:00:00.000Z"}

            Até previous_valid_until (24 horas por padrão), ambos os segredos assinam: cada entrega traz v1=<new>,v1=<old>. Implante o novo segredo no seu receptor dentro dessa janela; como o verificador aceita qualquer entrada correspondente, nada é perdido durante a troca. Rotacionar de novo dentro da janela mantém todos os segredos não expirados. A rotação se aplica apenas a assinaturas de webhook (409 wrong_delivery_mode caso contrário).

            Mensagens mortas e reenvio

            GET /v1/workspaces/{workspace}/dead?limit=50 lista as mensagens mortas, da mais nova para a mais antiga, com o evento, attempts, dead_at, replay_count e last_error:

            last_errorSignificado
            webhook_status_<code>O seu endpoint respondeu esse status, por exemplo webhook_status_500.
            webhook_timeoutNenhuma resposta em 10 segundos.
            webhook_networkA conexão falhou.
            url_destination_blockedA URL agora aponta para um lugar aonde as entregas não podem ir.
            secret_missingO segredo de assinatura da assinatura não pôde ser usado.

            Corrija a causa e depois faça POST /v1/workspaces/{workspace}/dead/{event_id}/replay. A resposta é 202, e o mesmo envelope, com o mesmo event_id, é entregue de novo às assinaturas que nunca o receberam. As mensagens mortas são mantidas por 30 dias.

            Boas práticas

            • Verifique antes de interpretar: calcule a assinatura sobre os bytes brutos e só depois interprete.
            • Torne o receptor idempotente: guarde cada x-event-id processado e responda 2xx imediatamente para um que você já viu.
            • Responda rápido: confirme com um 2xx e faça o trabalho lento depois, a partir de uma tarefa sua.
            • Rejeite carimbos de data e hora obsoletos (a mais de cinco minutos de diferença) e mantenha o relógio do seu servidor sincronizado.
            • Mantenha o segredo em um gerenciador de segredos e rotacione-o quando alguém que o conhecia sair, ou em um calendário.
            • Monitore as mensagens mortas e dlq_inflow em GET /stats; reenvie quando o endpoint estiver saudável.
            • Devolva 2xx apenas quando tiver o evento em segurança: um 2xx encerra as novas tentativas.

            Veja também

            Atualizado em 2026-10-10.