Functions de type hook
Écrivez une function qui s'exécute au sein d'une opération OmniLab et détermine si celle-ci se poursuit, puis liez-la à un point d'accroche.
Une function de type hook s'exécute pendant une opération, pas après. OmniLab suspend l'opération, appelle votre code et applique votre réponse : poursuivre, éventuellement avec des valeurs que vous fournissez, ou refuser. C'est pourquoi une function de type hook demande plus de soin qu'une function d'événement de plateforme.
Quand choisir une function de type hook
Choisissez une function de type hook quand le résultat d'une opération doit dépendre de votre propre logique. Par exemple : bloquer les inscriptions venant de domaines d'email jetables, normaliser des données avant leur stockage, ou envoyer l'email de vérification via votre propre fournisseur. Pour réagir à quelque chose qui s'est déjà produit, utilisez plutôt une function d'événement de plateforme.
Les deux points d'accroche
Une function de type hook est créée pour un seul point d'accroche et ne peut être liée qu'à celui-là.
| Point d'accroche | S'exécute quand | Votre export | continue vous permet de |
|---|---|---|---|
contact.create.pre | Avant qu'un contact soit stocké, qu'il provienne d'une inscription, d'un administrateur, d'un import ou de l'API | onContactCreatePre | Réécrire l'email, le numéro de téléphone et les champs personnalisés |
contact.verify | Une inscription en double opt-in doit recevoir son email de vérification | onContactVerify | Transmettre l'identifiant du message attribué par votre fournisseur, ou signaler que l'adresse est déjà vérifiée |
Ce que garantit un hook
| Quand | contact.create.pre | contact.verify |
|---|---|---|
| Aucune liaison active ne couvre l'organisation | Le contact est stocké tel que soumis | L'inscription échoue, et les campagnes de cette organisation ne peuvent pas être publiées tant que le double opt-in est activé |
Votre gestionnaire retourne continue | Les champs retournés sont appliqués, puis le contact est stocké | Le visiteur attend le lien que vous avez envoyé. Avec already_verified: true, le contact est directement créé comme vérifié |
Votre gestionnaire retourne deny | Le contact est refusé avec votre code, ou DENIED si le code est vide | L'inscription échoue |
| Votre gestionnaire lève une exception, dépasse une limite ou ne respecte pas le contrat, ou la function n'a jamais été compilée | Le contact est refusé | L'inscription échoue |
| Nouvelles tentatives | Aucune : l'opération attend votre réponse | Aucune |
| Pour annuler un gestionnaire défaillant | Désactivez la liaison : les contacts sont de nouveau stockés tels que soumis | Enregistrez et compilez la dernière version fonctionnelle de votre code. Ne désactivez pas la liaison tant que le double opt-in est activé |
Un refus est une exécution normale et réussie. La quatrième ligne est un échec, et en cas d'échec, un hook bloque l'opération : OmniLab la refuse plutôt que de la laisser passer sans contrôle.
Construisez votre réponse avec OmniLab.Flow.continue(result?) ou OmniLab.Flow.deny(code, message). Un simple objet de même structure convient aussi : le contrat des hooks précise ce qui est accepté.
contact.create.pre
Votre fichier exporte onContactCreatePre.
import { OmniLab } from "./omnilab";
import type { ContactCreatePreHandler } from "./omnilab";
export const onContactCreatePre: ContactCreatePreHandler = (input) => {
console.log("Hello world! A contact is being created: " + input.contact.email);
return OmniLab.Flow.continue();
};Cette version laisse passer tous les contacts sans les modifier : c'est la bonne première version. Liez-la, observez les exécutions qui apparaissent et vérifiez que les inscriptions fonctionnent toujours. Ensuite seulement, ajoutez une logique capable de refuser.
Refuser et réécrire
continue() sans argument ne change rien. Si vous passez un objet, OmniLab n'applique que les champs que vous retournez : un champ omis reste exactement tel qu'il a été soumis.
import { OmniLab } from "./omnilab";
import type { ContactCreatePreHandler } from "./omnilab";
export const onContactCreatePre: ContactCreatePreHandler = (input) => {
const email = input.contact.email.trim().toLowerCase();
if (email.endsWith("@blocked.example")) {
return OmniLab.Flow.deny("blocked_domain", "Email domain is on the blocklist");
}
// Seul `email` est retourné : le numéro de téléphone et les champs personnalisés sont stockés tels que soumis.
return OmniLab.Flow.continue({ email });
};Seul le code de votre refus est renvoyé à l'appelant. Le message reste dans le journal d'exécution : rédigez-le pour la personne qui lit le journal, pas pour le visiteur.
Normaliser à ce stade est payant, car le hook s'exécute avant le stockage du contact et avant la mise à jour de tout CRM connecté. L'email que vous retournez est celui qu'OmniLab stocke et transmet. Le passer en minuscules évite que Foo@example.com et foo@example.com ne donnent deux profils distincts.
Quand votre fournisseur est en panne
Si votre gestionnaire appelle un service tiers, prévoyez ce qui doit se passer quand ce service est indisponible. En cas d'échec, un hook bloque l'opération : si vous ne prévoyez rien, une panne du fournisseur bloque les inscriptions de toutes les organisations couvertes par la liaison.
Interceptez l'échec et poursuivez. Un fournisseur injoignable ne prouve pas qu'un client est un fraudeur :
import { OmniLab } from "./omnilab";
import type { ContactCreatePreHandler } from "./omnilab";
export const onContactCreatePre: ContactCreatePreHandler = async (input) => {
const email = input.contact.email.trim().toLowerCase();
try {
const response = await OmniLab.Http.get(
"https://api.example-checker.com/v1/check?email=" + encodeURIComponent(email),
);
const verdict = await response.json<{ disposable: boolean }>();
if (verdict.disposable) {
return OmniLab.Flow.deny("disposable_email", "Checker flagged the address as disposable");
}
} catch (error) {
// Le service de vérification est injoignable. On laisse passer l'inscription plutôt
// que de bloquer un vrai client à cause de la panne d'un autre.
console.warn("email check unavailable, allowing sign-up: " + String(error));
}
return OmniLab.Flow.continue({ email });
};Ne refusez qu'en cas de réponse négative certaine. Traitez « je n'ai pas pu obtenir de réponse » comme une raison de poursuivre.
try/catch intercepte l'échec d'une requête HTTP, pas le dépassement du délai de votre gestionnaire. Réglez le délai de la function au-dessus du temps de réponse le plus lent de votre fournisseur : voir Limites d'exécution.
contact.verify
Votre fichier exporte onContactVerify. OmniLab génère le lien de double opt-in, et votre gestionnaire l'envoie via votre fournisseur d'email.
Commencez par vérifier le branchement sans fournisseur. Le lien arrive dans input.verification_url : écrivez-le dans le journal.
import { OmniLab } from "./omnilab";
import type { ContactVerifyHandler } from "./omnilab";
export const onContactVerify: ContactVerifyHandler = (input) => {
console.log("Hello world! Verification link for " + input.contact.email);
console.log(input.verification_url);
return OmniLab.Flow.continue();
};Inscrivez un contact de test, ouvrez le journal d'exécution et copiez le lien depuis le panneau Journaux pour terminer la vérification à la main.
À remplacer avant la mise en production
Poursuivre sans rien envoyer indique à OmniLab que l'email est parti alors que ce n'est pas le cas : les vrais contacts ne reçoivent jamais de lien. N'utilisez cette version que pendant la mise en place de la function.
Envoyer via votre fournisseur
OmniLab ne réessaie jamais un appel de hook. Chaque envoi, y compris un renvoi demandé par le contact, comporte une nouvelle input.context.idempotency_key. Transmettez-la si votre fournisseur accepte une clé d'idempotence ; sinon, vous pouvez l'ignorer.
Le contexte indique aussi d'où vient l'inscription. context.interaction_id et context.interaction_public_key identifient la campagne, et context.group_id l'organisation pour laquelle la liaison a été résolue. Une même function peut s'en servir pour adapter son email à chaque campagne : inutile de maintenir une function par campagne.
Construisez la salutation avec contactDisplayName plutôt qu'avec input.contact.firstname. Un contact comporte soit firstname + lastname, soit un seul fullname, selon la façon dont le formulaire d'inscription de la campagne recueille les noms. Un gestionnaire qui ne lit que firstname envoie « Bonjour , » à tous les contacts d'une campagne qui recueille un nom complet.
import { OmniLab, contactDisplayName } from "./omnilab";
import type { ContactVerifyHandler } from "./omnilab";
export const onContactVerify: ContactVerifyHandler = async (input) => {
// Lit fullname, ou firstname + lastname, selon ce que comporte le contact.
const name = contactDisplayName(input.contact);
// Les valeurs de la configuration du plugin sont des chaînes, et Brevo attend un nombre.
const templateId = Number(OmniLab.Config.get("brevo_template_id"));
if (!Number.isInteger(templateId) || templateId < 1) {
throw new Error("Set brevo_template_id in Plugin config to a Brevo template id");
}
const response = await OmniLab.Http.post("https://api.brevo.com/v3/smtp/email", {
headers: {
"content-type": "application/json",
"api-key": OmniLab.Config.getSecret("brevo.API_KEY"),
},
body: JSON.stringify({
to: [{ email: input.contact.email, name }],
templateId,
params: { NAME: name, VERIFICATION_URL: input.verification_url },
}),
});
if (!response.ok) {
return OmniLab.Flow.deny("dispatch_failed", "Brevo refused the send: HTTP " + response.status);
}
const sent = await response.json<{ messageId?: string }>();
return OmniLab.Flow.continue({ provider_message_id: sent.messageId ?? "" });
};Ce code nécessite api.brevo.com dans les hôtes autorisés, une clé brevo_template_id dans la carte Configuration du plugin, et un lot de secrets brevo associé, contenant API_KEY. Ces trois éléments se configurent comme indiqué dans Configurer l'exécution et les secrets. Sans identifiant de modèle valide, le gestionnaire lève une exception : l'inscription échoue et le journal d'exécution indique pourquoi.
Poursuivre signifie expédié, pas délivré. continue indique à OmniLab que votre fournisseur a accepté le message, pas qu'il est arrivé. Ne refusez que si l'expédition elle-même a échoué, car un refus fait échouer l'inscription.
Ne pas envoyer l'email si le contact est déjà vérifié
Vos propres données contiennent peut-être déjà un opt-in confirmé pour cette adresse, issu par exemple d'une campagne précédente. Retournez alors already_verified: true au lieu d'envoyer l'email : OmniLab crée le contact comme vérifié sur la foi de votre preuve. Ne le faites qu'à partir d'un double opt-in confirmé, jamais d'une simple présence dans une liste importée : vous vous portez garant du consentement.
import { OmniLab, contactDisplayName } from "./omnilab";
import type { ContactVerifyHandler } from "./omnilab";
// Demande à votre propre système si cette adresse a déjà terminé le double opt-in.
// Tout échec compte comme "non prouvé" : l'email est alors envoyé normalement.
async function hasConfirmedOptIn(email: string): Promise<boolean> {
try {
const response = await OmniLab.Http.get(
"https://crm.lindenhall.example/v1/optins?email=" + encodeURIComponent(email),
{ headers: { authorization: "Bearer " + OmniLab.Config.getSecret("crm.API_TOKEN") } },
);
if (!response.ok) {
return false;
}
const record = await response.json<{ confirmed?: boolean }>();
return record.confirmed === true;
} catch (error) {
console.warn("opt-in lookup failed, sending the email: " + String(error));
return false;
}
}
export const onContactVerify: ContactVerifyHandler = async (input) => {
if (await hasConfirmedOptIn(input.contact.email)) {
return OmniLab.Flow.continue({ already_verified: true });
}
const name = contactDisplayName(input.contact);
const templateId = Number(OmniLab.Config.get("brevo_template_id"));
if (!Number.isInteger(templateId) || templateId < 1) {
throw new Error("Set brevo_template_id in Plugin config to a Brevo template id");
}
const response = await OmniLab.Http.post("https://api.brevo.com/v3/smtp/email", {
headers: {
"content-type": "application/json",
"api-key": OmniLab.Config.getSecret("brevo.API_KEY"),
},
body: JSON.stringify({
to: [{ email: input.contact.email, name }],
templateId,
params: { NAME: name, VERIFICATION_URL: input.verification_url },
}),
});
if (!response.ok) {
return OmniLab.Flow.deny("dispatch_failed", "Brevo refused the send: HTTP " + response.status);
}
const sent = await response.json<{ messageId?: string }>();
return OmniLab.Flow.continue({ provider_message_id: sent.messageId ?? "" });
};En plus de la configuration Brevo, ce code nécessite crm.lindenhall.example dans les hôtes autorisés et un lot de secrets crm contenant API_TOKEN.
Lier la function au point d'accroche
Choisissez d'abord la portée :
| Portée | Quand l'utiliser | Comment la définir |
|---|---|---|
| Global | La function doit s'exécuter pour toutes les organisations | L'option Global (tous les groupes) activée |
| Groupes précis | Seules certaines organisations en ont besoin | Le champ Groupes cibles |
Une liaison est active dès sa création
Il n'existe ni liaison en brouillon, ni liaison en mode journal seul : en quelques secondes, les opérations couvertes commencent à attendre votre function. Testez-la d'abord, puis liez une seule organisation à faible trafic avant de créer une liaison globale.
Dans l'éditeur de la function :
-
Sélectionnez l'onglet Hooks.

-
Sélectionnez Ajouter une liaison.

Le champ Point d'accroche est fixé par la function et s'affiche en lecture seule.
-
Sous Groupes cibles, sélectionnez Lindenhall Shopping Centre.

-
Sélectionnez Créer.

La liaison est créée au statut Actif. Pour couvrir toutes les organisations, activez plutôt l'option Global (tous les groupes) à l'étape 3.
Le formulaire applique les règles de portée :
- Au maximum une liaison globale active par point d'accroche. S'il en existe une, l'interrupteur est désactivé et indique pourquoi.
- Un groupe appartient à au maximum une liaison active par point d'accroche. Les groupes déjà couverts sont grisés.
- Une liaison propre à des groupes l'emporte sur la liaison globale pour ces groupes. Utilisez une liaison globale pour le comportement par défaut, et des liaisons par groupe pour les exceptions.
Une liaison ne se modifie pas : pour changer ses organisations, supprimez-la et créez-en une nouvelle. Pour contact.verify, créez la liaison avant qu'un administrateur active le double opt-in : voir Vérification email en double opt-in.
Retirer une liaison du parcours en production
L'effet dépend du point d'accroche : voir la dernière ligne de Ce que garantit un hook. Pour contact.verify, désactiver la liaison fait échouer toutes les inscriptions en double opt-in.
Dans l'éditeur de la function :
- Sélectionnez l'onglet Hooks.
- Sur la ligne de la liaison, sélectionnez Désactiver.
Le bouton Activer la rétablit. Le bouton Supprimer retire la liaison mais conserve la function. La carte de la function affiche non lié quand une function de type hook n'a aucune liaison active.
En cas de blocage
- L'onglet Hooks est absent : il n'apparaît que sur les functions de type hook. Vérifiez le badge de type en haut de l'éditeur.
- L'interrupteur global est désactivé : une autre liaison globale active couvre ce point d'accroche. Désactivez-la, ou ciblez plutôt des organisations précises.
- Un groupe est grisé dans Groupes cibles : une autre liaison active sur ce point d'accroche le couvre déjà.
- Les inscriptions échouent depuis que vous avez créé une liaison : revenez en arrière comme l'indique le tableau des garanties pour votre point d'accroche, puis lisez le journal d'exécution.
- Les exécutions semblent réussies mais les opérations sont refusées, ou un test affiche Violation de contrat : la réponse de votre gestionnaire ne respecte pas le contrat. Voir Lire une violation de contrat de hook.
- L'email de vérification n'arrive jamais : ouvrez le détail de l'exécution.
EGRESS_BLOCKEDsignifie que l'hôte du fournisseur n'est pas autorisé.PERMISSION_DENIEDouNOT_FOUNDrenvoyé pargetSecretsignale un problème de lot de secrets ou de clé : voir Configurer l'exécution et les secrets.
Étapes suivantes
Functions d'événement de plateforme
Écrivez une function qui s'exécute de façon asynchrone après un événement dans OmniLab, et abonnez-la aux types d'événements auxquels elle doit réagir.
Configurer l'exécution et les secrets
Délai et mémoire d'une function, hôtes sortants autorisés, clés de configuration et identifiants rangés dans des lots de secrets.