Referência do SDK
O pacote @ikiform/sdk: submit, challenge, getSchema, validate, createClient e verifyWebhook.
@ikiform/sdk é um pequeno pacote TypeScript para enviar formulários, ler dados da API REST e verificar webhooks. Ele não tem dependências e usa só fetch, FormData e Web Crypto, então roda em navegadores, Node 20+, Bun, Deno e Cloudflare Workers. Ele é distribuído como ESM com declarações de tipo.
npm install @ikiform/sdk| Import | De | Roda em |
|---|---|---|
submit | @ikiform/sdk | Navegador ou servidor |
challenge, ChallengeError | @ikiform/sdk | Navegador |
getSchema, validate, validateField | @ikiform/sdk | Navegador ou servidor |
createClient, IkiformError | @ikiform/sdk | Só servidor |
verifyWebhook, WebhookVerificationError | @ikiform/sdk/webhooks | Servidor |
submit
submit(formId, data, options?) envia para /f/:formId e resolve para um resultado. Ele nunca lança exceção: erros HTTP retornam o envelope de erro, falhas de rede retornam o código network_error e cancelamentos retornam aborted.
type SubmitResult =
| { ok: true; id: string }
| {
ok: false;
error: { code: string; message: string; fields?: Record<string, string[]> };
status: number | null;
requestId: string | null;
};| data | Enviado como |
|---|---|
FormData só com texto | application/x-www-form-urlencoded |
FormData com um File | multipart/form-data, que o Ikiform recusa até o upload de arquivos existir |
URLSearchParams | application/x-www-form-urlencoded |
| Um objeto simples | application/json |
- Os campos
$ACTION_do React são descartados doFormData. - O valor retornado também satisfaz o tipo de form action do React, então
action={(data) => submit(id, data)}funciona. submitenviaAccept: application/json. Ele não envia um headerAuthorization; usefetchpara enviar com uma chave de API.
| Opção | Descrição |
|---|---|
baseUrl | O padrão é https://ikiform.com. |
signal | Um AbortSignal para cancelar a requisição. |
idempotencyKey | Enviado como o header Idempotency-Key. |
turnstileToken | Enviado como cf-turnstile-response em qualquer formato de corpo usado. |
fetch | Uma implementação personalizada de fetch. |
import { submit } from "@ikiform/sdk";
const result = await submit("frm_xxxxxxxxxxxxxxxx", { email: "ada@example.com" }, {
idempotencyKey: crypto.randomUUID(),
signal: AbortSignal.timeout(10_000),
});
if (!result.ok) console.error(result.error.code, result.error.fields);challenge
Para formulários com a verificação de spam Ikiform. challenge(formId, options?) monta um frame de ikiform.com com 300 pixels de largura que fica com altura zero, a menos que o Turnstile precise de um clique, resolve com um token e remove o frame. Os tokens são de uso único e valem por 300 segundos, então chame uma vez por envio.
| Opção | Descrição |
|---|---|
container | Onde montar o frame. O padrão é document.body. |
mode | "frame" (padrão) ou "popup", para navegadores que bloqueiam o frame. O popup precisa ser aberto a partir de um clique. |
timeoutMs | Rejeita depois deste número de milissegundos. Sem timeout por padrão. |
signal | Um AbortSignal. |
baseUrl | O padrão é https://ikiform.com. |
Ele rejeita com ChallengeError, cujo code é challenge_failed, challenge_timeout ou aborted.
import { challenge, ChallengeError, submit } from "@ikiform/sdk";
async function send(form) {
try {
const turnstileToken = await challenge("frm_xxxxxxxxxxxxxxxx", { timeoutMs: 60_000 });
return submit("frm_xxxxxxxxxxxxxxxx", new FormData(form), { turnstileToken });
} catch (error) {
if (!(error instanceof ChallengeError) || error.code !== "challenge_failed") throw error;
const turnstileToken = await challenge("frm_xxxxxxxxxxxxxxxx", { mode: "popup" });
return submit("frm_xxxxxxxxxxxxxxxx", new FormData(form), { turnstileToken });
}
}getSchema
getSchema(formId, options?) busca o schema público do formulário: os campos publicados, as configurações de exibição, o nome do honeypot e o modo de verificação. Assim como submit, ele nunca lança exceção e resolve para { ok: true, schema } ou um resultado de erro. As opções são baseUrl, signal e fetch. A resposta está descrita na página do shadcn/ui.
validate
validate(fields, data) confere um FormData ou um objeto simples contra as definições de campo e retorna Record<string, string>, uma mensagem por campo inválido, ou um objeto vazio. Ele usa as mesmas regras e mensagens do servidor para os 12 tipos de campo. validateField(field, value) confere um valor e retorna uma mensagem ou undefined.
import { getSchema, submit, validate } from "@ikiform/sdk";
const result = await getSchema("frm_xxxxxxxxxxxxxxxx");
if (result.ok) {
const errors = validate(result.schema.fields, new FormData(form));
if (Object.keys(errors).length === 0) {
await submit("frm_xxxxxxxxxxxxxxxx", new FormData(form));
}
}createClient
createClient({ apiKey, baseUrl?, fetch? }) retorna um client para a API REST. Ele lança um TypeError se a chave não for secreta, então mantenha-o no servidor.
| Método | Requisição |
|---|---|
forms.list({ limit, cursor }) | GET /forms |
forms.get(formId) | GET /forms/:formId |
forms.iterate({ limit }) | Todos os formulários, seguindo os cursores |
submissions.list({ formId, status, limit, cursor }) | GET /submissions |
submissions.get(id) | GET /submissions/:id |
submissions.iterate({ formId, status, limit }) | Todos os envios, seguindo os cursores |
submissions.markSpam(id) | POST /submissions/:id/spam |
submissions.markNotSpam(id) | POST /submissions/:id/not-spam |
submissions.delete(id) | DELETE /submissions/:id |
Falhas lançam IkiformError com code, status, requestId, message, mais fields quando presente e retryAfter em segundos em caso de 429. Falhas de rede lançam com code: "network_error" e status: 0.
import { createClient, IkiformError } from "@ikiform/sdk";
const ikiform = createClient({ apiKey: process.env.IKIFORM_SECRET_KEY! });
const forms = await ikiform.forms.list({ limit: 10 });
const form = await ikiform.forms.get("frm_xxxxxxxxxxxxxxxx");
for await (const submission of ikiform.submissions.iterate({ formId: form.id, status: "received" })) {
console.log(submission.id, submission.fields);
}
try {
await ikiform.submissions.delete("sub_01k7v2c9t3e8m5q4x6z0a1b2c3");
} catch (error) {
if (error instanceof IkiformError && error.status === 429) {
console.log(`Retry in ${error.retryAfter} seconds`);
}
}verifyWebhook
verifyWebhook({ payload, headers, secret, toleranceSeconds? }) verifica a assinatura de um webhook e retorna o evento já interpretado. Passe a string do corpo bruto. A tolerância padrão é de 300 segundos nas duas direções.
import { verifyWebhook, type WebhookEvent } from "@ikiform/sdk/webhooks";
const event = await verifyWebhook<WebhookEvent>({
payload: rawBody,
headers: request.headers,
secret: process.env.IKIFORM_WEBHOOK_SECRET!,
toleranceSeconds: 300,
});Erros de verificação
| reason | Significado |
|---|---|
invalid_secret | O segredo não é whsec_ mais base64 |
missing_headers | Falta webhook-id, webhook-timestamp ou webhook-signature |
invalid_timestamp | webhook-timestamp não está em segundos Unix inteiros |
timestamp_too_old | Assinado há mais de toleranceSeconds, por exemplo uma requisição repetida |
timestamp_too_new | Assinado no futuro; confira o relógio do seu servidor |
no_matching_signature | Nenhuma assinatura v1 corresponde: segredo errado, ou o corpo foi alterado |
invalid_payload | A assinatura correspondeu, mas o corpo não é JSON |
WebhookEvent, SubmissionCreatedEvent e EndpointTestEvent. Veja Webhooks para o payload completo.