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.
npm install @ikiform/sdk| Import | Depuis | S'exécute dans |
|---|---|---|
submit | @ikiform/sdk | Navigateur ou serveur |
challenge, ChallengeError | @ikiform/sdk | Navigateur |
getSchema, validate, validateField | @ikiform/sdk | Navigateur ou serveur |
createClient, IkiformError | @ikiform/sdk | Serveur uniquement |
verifyWebhook, WebhookVerificationError | @ikiform/sdk/webhooks | Serveur |
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.
type SubmitResult =
| { ok: true; id: string }
| {
ok: false;
error: { code: string; message: string; fields?: Record<string, string[]> };
status: number | null;
requestId: string | null;
};| data | Envoyé en |
|---|---|
FormData avec uniquement du texte | application/x-www-form-urlencoded |
FormData avec un File | multipart/form-data, refusé par Ikiform tant que l'envoi de fichiers n'existe pas |
URLSearchParams | application/x-www-form-urlencoded |
| Un objet simple | application/json |
- Les champs
$ACTION_de React sont retirés duFormData. - La valeur renvoyée satisfait aussi le type d'action de formulaire de React, donc
action={(data) => submit(id, data)}fonctionne. submitenvoieAccept: application/json. Il n'envoie pas d'en-têteAuthorization; utilisezfetchpour envoyer avec une clé API.
| Option | Description |
|---|---|
baseUrl | Par défaut https://ikiform.com. |
signal | Un AbortSignal pour annuler la requête. |
idempotencyKey | Envoyé dans l'en-tête Idempotency-Key. |
turnstileToken | Envoyé en cf-turnstile-response, quel que soit le format du corps. |
fetch | Une implémentation personnalisée 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
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.
| Option | Description |
|---|---|
container | Où 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. |
timeoutMs | Rejette après ce nombre de millisecondes. Pas de timeout par défaut. |
signal | Un AbortSignal. |
baseUrl | Par défaut https://ikiform.com. |
Il est rejeté avec une ChallengeError, dont le code vaut 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?) 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.
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éthode | Requê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.
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.
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
| reason | Signification |
|---|---|
invalid_secret | Le secret n'est pas whsec_ suivi de base64 |
missing_headers | webhook-id, webhook-timestamp ou webhook-signature est absent |
invalid_timestamp | webhook-timestamp n'est pas un nombre entier de secondes Unix |
timestamp_too_old | Signé il y a plus de toleranceSeconds, par exemple une requête rejouée |
timestamp_too_new | Signé dans le futur ; vérifiez l'horloge de votre serveur |
no_matching_signature | Aucune signature v1 ne correspond : mauvais secret, ou corps modifié |
invalid_payload | La signature correspond mais le corps n'est pas du JSON |
WebhookEvent, SubmissionCreatedEvent et EndpointTestEvent. Voir Webhooks pour le payload complet.