ID du formulaire

Référence du SDK

Le package @ikiform/sdk : submit, challenge, getSchema, validate, createClient et verifyWebhook.

@ikiform/sdk est un petit package TypeScript pour soumettre des formulaires, lire des données via l'API REST et vérifier les webhooks. Il n'a aucune dépendance et n'utilise que fetch, FormData et Web Crypto, donc il tourne dans les navigateurs, Node 20+, Bun, Deno et Cloudflare Workers. Il est distribué en ESM avec ses déclarations de types.

Terminal
npm install @ikiform/sdk
ImportDepuisS'exécute dans
submit@ikiform/sdkNavigateur ou serveur
challenge, ChallengeError@ikiform/sdkNavigateur
getSchema, validate, validateField@ikiform/sdkNavigateur ou serveur
createClient, IkiformError@ikiform/sdkServeur uniquement
verifyWebhook, WebhookVerificationError@ikiform/sdk/webhooksServeur

submit

submit(formId, data, options?) envoie vers /f/:formId et se résout en un résultat. Il ne lève jamais d'exception : les erreurs HTTP renvoient l'enveloppe d'erreur, les pannes réseau renvoient le code network_error et les interruptions renvoient aborted.

SubmitResult
type SubmitResult =
  | { ok: true; id: string }
  | {
      ok: false;
      error: { code: string; message: string; fields?: Record<string, string[]> };
      status: number | null;
      requestId: string | null;
    };
dataEnvoyé en
FormData avec uniquement du texteapplication/x-www-form-urlencoded
FormData avec un Filemultipart/form-data, refusé par Ikiform tant que l'envoi de fichiers n'existe pas
URLSearchParamsapplication/x-www-form-urlencoded
Un objet simpleapplication/json
  • Les champs $ACTION_ de React sont retirés du FormData.
  • La valeur renvoyée satisfait aussi le type d'action de formulaire de React, donc action={(data) => submit(id, data)} fonctionne.
  • submit envoie Accept: application/json. Il n'envoie pas d'en-tête Authorization ; utilisez fetch pour envoyer avec une clé API.
OptionDescription
baseUrlPar défaut https://ikiform.com.
signalUn AbortSignal pour annuler la requête.
idempotencyKeyEnvoyé dans l'en-tête Idempotency-Key.
turnstileTokenEnvoyé en cf-turnstile-response, quel que soit le format du corps.
fetchUne implémentation personnalisée de fetch.
submit.ts
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

Pour les formulaires dont le contrôle anti-spam est Ikiform. challenge(formId, options?) monte une frame ikiform.com de 300 pixels de large qui garde une hauteur nulle sauf si Turnstile a besoin d'un clic, se résout avec un token et retire la frame. Les tokens sont à usage unique et valables 300 secondes : appelez-le une fois par envoi.

OptionDescription
containerOù monter la frame. Par défaut document.body.
mode"frame" (par défaut) ou "popup", pour les navigateurs qui bloquent la frame. La popup doit être ouverte depuis un clic.
timeoutMsRejette après ce nombre de millisecondes. Pas de timeout par défaut.
signalUn AbortSignal.
baseUrlPar défaut https://ikiform.com.

Il est rejeté avec une ChallengeError, dont le code vaut challenge_failed, challenge_timeout ou aborted.

challenge.ts
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?) récupère le schéma public du formulaire : ses champs publiés, ses réglages d'affichage, le nom du honeypot et le mode de vérification. Comme submit, il ne lève jamais d'exception et se résout en { ok: true, schema } ou en résultat d'erreur. Les options sont baseUrl, signal et fetch. La réponse est décrite sur la page shadcn/ui.

validate

validate(fields, data) vérifie un FormData ou un objet simple par rapport aux définitions de champs et renvoie un Record<string, string>, avec un message par champ invalide, ou un objet vide. Il utilise les mêmes règles et messages que le serveur pour les 12 types de champs. validateField(field, value) vérifie une seule valeur et renvoie un message ou undefined.

validate.ts
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? }) renvoie un client pour l'API REST. Il lève une TypeError si la clé n'est pas une clé secrète : gardez-le donc côté serveur.

MéthodeRequête
forms.list({ limit, cursor })GET /forms
forms.get(formId)GET /forms/:formId
forms.iterate({ limit })Tous les formulaires, en suivant les curseurs
submissions.list({ formId, status, limit, cursor })GET /submissions
submissions.get(id)GET /submissions/:id
submissions.iterate({ formId, status, limit })Toutes les soumissions, en suivant les curseurs
submissions.markSpam(id)POST /submissions/:id/spam
submissions.markNotSpam(id)POST /submissions/:id/not-spam
submissions.delete(id)DELETE /submissions/:id

Les échecs lèvent une IkiformError avec code, status, requestId, message, plus fields le cas échéant et retryAfter en secondes sur un 429. Les pannes réseau lèvent une erreur avec code: "network_error" et status: 0.

client.ts
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? }) vérifie une signature de webhook et renvoie l'événement analysé. Passez la chaîne du corps brut. La tolérance par défaut est de 300 secondes dans les deux sens.

webhook.ts
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,
});

Erreurs de vérification

reasonSignification
invalid_secretLe secret n'est pas whsec_ suivi de base64
missing_headerswebhook-id, webhook-timestamp ou webhook-signature est absent
invalid_timestampwebhook-timestamp n'est pas un nombre entier de secondes Unix
timestamp_too_oldSigné il y a plus de toleranceSeconds, par exemple une requête rejouée
timestamp_too_newSigné dans le futur ; vérifiez l'horloge de votre serveur
no_matching_signatureAucune signature v1 ne correspond : mauvais secret, ou corps modifié
invalid_payloadLa signature correspond mais le corps n'est pas du JSON
Les types d'événements sont exportés sous les noms WebhookEvent, SubmissionCreatedEvent et EndpointTestEvent. Voir Webhooks pour le payload complet.