ID do formulário

Webhooks

Payloads de webhook assinados, verificação da assinatura HMAC-SHA256, a agenda de novas tentativas e os logs de entrega.

Os webhooks repassam cada envio aceito para o seu servidor assim que ele chega. As entregas são assinadas com o esquema Standard Webhooks, têm novas tentativas por cerca de três dias e ficam registradas tentativa por tentativa no painel. Há handlers prontos em Recebendo webhooks.

Adicione um endpoint

  1. No painel, abra Webhooks e escolha Add endpoint.
  2. Informe uma URL HTTPS e escolha um formulário, ou todos os formulários do workspace.
  3. Copie o segredo de assinatura. Ele começa com whsec_ e só é exibido por completo uma vez.
  4. Envie um evento de teste para conferir o seu handler.

URLs de endpoint

  • Só https:, na porta padrão 443, sem usuário ou senha na URL, com até 2.048 caracteres.
  • Um nome de host público. Endereços IP, localhost e nomes terminados em .local, .internal, .intranet, .lan, .home.arpa ou .onion são recusados, assim como ikiform.com, workers.dev e pages.dev.
  • O nome de host precisa resolver só para endereços públicos. Faixas privadas, de loopback, link-local e de NAT carrier-grade são recusadas.
  • A URL é verificada quando você a salva e de novo antes de cada entrega. Um endpoint que passa a resolver para um endereço privado é desativado.
  • Um workspace pode ter até 3 endpoints. O Enterprise não tem limite.

Eventos

TipoEnviado quandoNovas tentativas
submission.createdUm envio é aceito, ou um envio marcado como spam é marcado como não spamSim
endpoint.testVocê escolhe Send test na página do endpointNão, uma tentativa

Spam nunca dispara um webhook. Envios retidos porque o workspace passou do limite mensal também não.

A requisição

Cada entrega é um POST com corpo JSON e três headers de assinatura, enviado pelo serviço de entrega do Ikiform com o user agent Ikiform-Webhooks/1.0.

Headers
POST /webhooks/ikiform HTTP/1.1
content-type: application/json
user-agent: Ikiform-Webhooks/1.0
webhook-id: dlv_01k7v2d4n8r2p6s0w3y5b7c9e1
webhook-timestamp: 1792300000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
HeaderValor
webhook-idO id da entrega. É o mesmo em toda nova tentativa da mesma entrega, então use-o para ignorar duplicados
webhook-timestampSegundos Unix de quando esta tentativa foi assinada. Novo a cada tentativa
webhook-signaturev1, mais um HMAC-SHA256 em base64. Vários valores separados por espaço enquanto um segredo é trocado

Payloads

{
  "id": "dlv_01k7v2d4n8r2p6s0w3y5b7c9e1",
  "type": "submission.created",
  "timestamp": "2026-10-17T12:00:00.000Z",
  "api_version": "2026-10-17",
  "data": {
    "submission_id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3",
    "form": { "id": "frm_xxxxxxxxxxxxxxxx", "name": "Contact" },
    "fields": { "name": "Ada", "email": "ada@example.com", "message": "Hello" },
    "meta": {
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
      "origin": "https://example.com",
      "referrer": "https://example.com/contact",
      "country": "GB",
      "subject": null,
      "reply_to": "ada@example.com"
    },
    "created_at": "2026-10-17T12:00:00.000Z",
    "truncated": false
  }
}
CampoDescrição
idO id da entrega, igual a webhook-id
typesubmission.created ou endpoint.test
timestampQuando a entrega foi criada, em ISO 8601 UTC. O mesmo em toda nova tentativa
api_versionA versão do payload com que o endpoint foi criado, atualmente 2026-10-17
data.submission_idO id do envio. Busque-o com a API REST
data.formO id e o name do formulário
data.fieldsOs valores armazenados. Com campos definidos, são os valores validados e convertidos
data.metaAs mesmas chaves do meta da API REST. Todas as chaves estão presentes e qualquer valor pode ser null. O IP do visitante e o hash dele nunca são enviados
data.meta.user_agentO user agent do navegador, com até 512 caracteres
data.meta.origin, referrerDe onde o envio veio, quando o navegador os enviou
data.meta.countryCódigo de país com duas letras, vindo da Cloudflare
data.meta.subject, reply_toDos campos _subject e _replyto
data.created_atQuando o envio foi armazenado
data.truncatedtrue quando o corpo passaria de 256 KB, caso em que fields fica vazio

Qualquer valor de meta pode ser null. Trate os valores dos campos como entrada não confiável, como um envio de formulário para o seu próprio servidor.

Verifique a assinatura

Como a assinatura do webhook é calculada
webhook-id.webhook-timestamp.corpo bruto

HMAC-SHA256, com o segredo decodificado de base64 depois de whsec_ como chave

webhook-signature: v1,base64(signature)

  • Use o corpo bruto da requisição exatamente como foi recebido. Fazer parse e serializar o JSON de novo muda os bytes.
  • O header pode conter várias assinaturas separadas por espaço. Aceite a requisição se qualquer assinatura v1 corresponder.
  • Compare em tempo constante e rejeite timestamps a mais de 5 minutos do horário atual.

Com o SDK

verifyWebhook faz tudo isso com Web Crypto e retorna o evento já interpretado, ou lança WebhookVerificationError com um reason. Ele aceita um objeto Headers ou um record simples, como o req.headers do Node.

app/api/webhooks/ikiform/route.ts
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";

export async function POST(request: Request) {
  const payload = await request.text();
  try {
    const event = await verifyWebhook({
      payload,
      headers: request.headers,
      secret: process.env.IKIFORM_WEBHOOK_SECRET!,
    });
    if (event.type === "submission.created") {
      console.log(event.data.fields);
    }
    return new Response(null, { status: 204 });
  } catch (error) {
    if (error instanceof WebhookVerificationError) {
      return new Response(error.reason, { status: 400 });
    }
    throw error;
  }
}

Sem o SDK

Qualquer biblioteca de Standard Webhooks também funciona. Na mão, são poucas linhas:

import { createHmac, timingSafeEqual } from "node:crypto";

const secret = Buffer.from(process.env.IKIFORM_WEBHOOK_SECRET.replace(/^whsec_/, ""), "base64");

export function verify(body, headers) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const signatures = headers["webhook-signature"];
  if (!id || !timestamp || !signatures) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${id}.${timestamp}.${body}`).digest();
  return signatures.split(" ").some((entry) => {
    const [version, signature] = entry.split(",");
    if (version !== "v1" || !signature) return false;
    const received = Buffer.from(signature, "base64");
    return received.length === expected.length && timingSafeEqual(received, expected);
  });
}

Verificador de assinatura

Ignorar a janela de 5 minutos

Usa o verifyWebhook do SDK com Web Crypto no seu navegador. Nada do que você cola sai desta página.

Respondendo

Sua respostaO que o Ikiform faz
Qualquer 2xx em até 15 segundosMarca a entrega como bem-sucedida
410 GoneMarca a entrega como falha e desativa o endpoint na hora
429 ou 503 com Retry-AfterTenta de novo depois desse intervalo, mas não depois da próxima etapa da agenda
Um redirecionamentoNão é seguido. Conta como falha
Qualquer outro status, um timeout ou um erro de redeNovas tentativas conforme a agenda abaixo

Faça o trabalho demorado depois de responder, por exemplo em uma fila. Os primeiros 4 KB do corpo da sua resposta ficam salvos no log de entrega.

Novas tentativas

Uma tentativa que falha é repetida nesta agenda, cada intervalo com até 20% de jitter aleatório para cada lado. Depois da 9ª tentativa, cerca de 3 d 6 h após a primeira, a entrega é marcada como falha.

Agenda de novas tentativas de webhook, com o intervalo antes de cada tentativa em escala logarítmica
TentativaEspera antesDesde a primeira
  1. 1agora0 s
  2. 25 s5 s
  3. 35 min5 min
  4. 430 min35 min
  5. 52 h2 h 35 min
  6. 65 h7 h 35 min
  7. 710 h17 h 35 min
  8. 81 d1 d 18 h
  9. 91 d 12 h3 d 6 h

Barras escuras mostram a espera mais curta e barras claras a mais longa, com 20% de jitter para cada lado. Depois da tentativa 9, a entrega é marcada como falha.

Endpoints desativados

Se todas as entregas para um endpoint falharem por 5 dias, o endpoint é desativado e os proprietários do workspace recebem um e-mail. Uma resposta 410 Gone ou um destino que passa a ser privado o desativa na hora. Reative-o na página do endpoint depois de corrigir.

Logs de entrega

A página de cada endpoint lista as entregas com o status de cada uma. Expanda uma para ver todas as tentativas: quando rodou, se foi automática, manual ou um teste, o código de status, a duração e os primeiros 4 KB da sua resposta ou o erro. Reenvie qualquer entrega a partir dali. As tentativas ficam guardadas por 30 dias e as entregas por 90 dias.

Troca de segredos

Roll signing secret cria um novo segredo. Nas 24 horas seguintes, toda entrega é assinada com o segredo antigo e com o novo, então webhook-signature contém dois valores e você pode fazer deploy do novo segredo sem perder eventos.