Une balise. C'est tout.
Prérequis : le site est créé dans CONTROL et ses domaines sont déclarés. Voir le README.
À coller avant </body>, sur toutes les pages :
</body>
<script src="https://messagerie.inovertix.com/inodesk.js" data-site="INODSK_PUB_votre_cle" data-endpoint="https://messagerie.inovertix.com/api/inodesk" defer></script>
Le snippet exact, avec votre clé déjà en place, est dans CONTROL → INODESK → Sites & clés → onglet Intégration.
Le widget dessine sa bulle, lit la configuration du site (couleurs, logo, onglets, formulaires) et s'adapte. Sur téléphone, le panneau passe en plein écran. Il ne pose aucun cookie et échoue toujours en silence : quoi qu'il arrive, votre page continue de fonctionner.
Si vous voulez le formulaire dans votre page plutôt que dans une bulle :
<div id="contact" data-inodesk-form="contact"></div>
Le widget remplit lui-même le conteneur, dans son propre shadow DOM — aucune règle CSS de votre site ne peut le déformer, et aucune des siennes ne peut fuir chez vous. Le data-inodesk-form porte l'identifiant du formulaire (CONTROL → INODESK → Formulaires).
data-inodesk-form
Si vous ne voulez que le formulaire intégré, sans bulle permanente :
<script src="https://messagerie.inovertix.com/inodesk.js" data-site="INODSK_PUB_votre_cle" data-endpoint="https://messagerie.inovertix.com/api/inodesk" data-launcher="off" defer></script>
<button type="button" onclick="window.inodesk && window.inodesk('open')"> Nous contacter </button>
inodesk('open')
inodesk('open', 'support')
inodesk('close')
L'appel est sans effet si le widget n'a pas encore chargé — il ne lèvera jamais d'exception dans votre page.
Le portail hébergé est une page complète, servie par CONTROL, aux couleurs de votre marque. Un simple lien suffit :
<a href="https://plateforme.inovertix.com/care/votre-site">Nous contacter</a>
Et pour pointer directement sur un formulaire précis (utile pour un QR code, une campagne, une carte de visite) :
<a href="https://plateforme.inovertix.com/care/votre-site/f/contact">Demander un devis</a>
Ces URLs sont affichées dans l'onglet Intégration du site, prêtes à copier.
Envoyez son contenu à l'API publique. Le transport est choisi pour ne déclencher aucun préflight CORS : clé dans l'URL, corps en text/plain. La requête reste « simple » au sens du navigateur, donc un seul aller-retour.
text/plain
<form id="contact"> <input name="name" placeholder="Votre nom" required> <input name="email" type="email" placeholder="vous@exemple.com" required> <textarea name="message" placeholder="Votre message" required></textarea> <!-- Champ piège : invisible, hors tabulation, hors lecteurs d'écran. Aucun humain ne le remplit — un robot qui remplit tout, si. --> <div style="position:absolute;left:-9999px" aria-hidden="true"> <input type="text" name="inodesk_website" tabindex="-1" autocomplete="off"> </div> <button type="submit">Envoyer</button> </form> <script> (function () { var KEY = 'INODSK_PUB_votre_cle'; var API = 'https://messagerie.inovertix.com/api/inodesk'; var form = document.getElementById('contact'); // Horodatage d'affichage : le serveur range en « Spam » un formulaire renvoyé en // moins de trois secondes. Aucun humain ne remplit trois champs aussi vite. var renderedAt = Date.now(); form.addEventListener('submit', function (event) { event.preventDefault(); var data = Object.fromEntries(new FormData(form).entries()); fetch(API + '/submissions?k=' + encodeURIComponent(KEY), { method: 'POST', headers: { 'Content-Type': 'text/plain;charset=UTF-8' }, body: JSON.stringify({ form_slug: 'contact', data: { name: data.name, email: data.email, message: data.message }, inodesk_website: data.inodesk_website, rendered_at: renderedAt, source_url: window.location.href }) }) .then(function (r) { return r.json(); }) .then(function (json) { form.innerHTML = '<p>' + (json.message || 'Merci, votre message est bien parti.') + '</p>'; }) .catch(function () { alert("L'envoi n'a pas abouti. Réessayez dans un instant."); }); }); })(); </script>
La réponse est 202 dans tous les cas exploitables, y compris quand la soumission est classée en spam : c'est voulu. Un robot qui reçoit une erreur recommence ; un robot satisfait s'en va. Les suspectes sont rangées dans le dossier « Spam » de l'inbox et restaurables d'un clic.
Une balise, un script, rien d'autre :
<div data-inodesk-reviews data-site="INODSK_PUB_votre_cle" data-endpoint="https://messagerie.inovertix.com/api/inodesk" data-mode="wall"></div> <script src="https://messagerie.inovertix.com/inodesk-reviews.js" defer></script>
data-mode
wall
badge
data-limit
Fichier séparé du widget (~4 Ko) : une page « Ils nous ont fait confiance » n'a aucune raison de télécharger le panneau de contact complet. Comme le widget, il vit dans un shadow DOM — votre CSS ne peut pas le déformer, le sien ne peut pas fuir — et il échoue toujours en silence.
Seuls les avis publiés dans CONTROL apparaissent : la modération est obligatoire, rien ne se publie sans une action humaine. Aucun avis publié → le bloc n'affiche rien.
Ce script ne pose pas le balisage schema.org, parce qu'un JSON-LD injecté en JavaScript est lu de façon inégale par les moteurs. Sur un site statique, deux options :
Le plus simple — liez /care/{votre-site}/avis : la page portail est déjà brandée, balisée et indexable.
/care/{votre-site}/avis
Le plus complet — générez le JSON-LD à la construction du site (générateur statique, script de build) à partir de GET /api/inodesk/reviews?k=…, et posez-le dans le <head>. La recette est dans avis-seo.md.
GET /api/inodesk/reviews?k=…
<head>
const res = await fetch( "https://messagerie.inovertix.com/api/inodesk/order-lookup?k=INODSK_PUB_votre_cle", { method: "POST", // text/plain, comme les autres envois du widget : cela évite la requête // préliminaire CORS. Le serveur décode le JSON lui-même. headers: { "Content-Type": "text/plain" }, body: JSON.stringify({ order_number: numero, phone: telephone }), } ); const data = await res.json(); if (!res.ok) { // ⚠️ Afficher data.message TEL QUEL. Il est identique pour toutes les causes // d'échec — numéro inconnu, téléphone faux, IP verrouillée. Le préciser // transformerait cette page en confirmateur de commandes. erreur.textContent = data.message; return; } // data.order : number, status, status_label, placed_at, total, currency, city
N'ajoutez aucune latence conditionnelle avant d'afficher le résultat : le serveur applique déjà un plancher de temps de réponse, et un setTimeout posé seulement sur l'échec suffirait à trahir la différence.
setTimeout
curl -sS "https://messagerie.inovertix.com/api/inodesk/ping?k=VOTRE_CLE" répond {"ok":true,…} avec le bon nom de site.
curl -sS "https://messagerie.inovertix.com/api/inodesk/ping?k=VOTRE_CLE"
{"ok":true,…}
Le panneau s'ouvre au clic, se ferme avec Échap, est navigable au clavier (Tab passe sur chaque champ, jamais sur le champ piège).
#PREFIXE-1
Sur téléphone réel : panneau plein écran, formulaire utilisable, clavier qui ne masque pas le bouton d'envoi.
Les onglets activés dans CONTROL (Devis, Avis, Ma commande) apparaissent après un simple rechargement, sans modifier la balise du site.
data-inodesk-reviews
Suivi de commande : une vraie commande remonte son statut ; un couple faux renvoie le même message et le même délai qu'un numéro inconnu.
/config
origin_not_allowed
https://exemple.com
https://www.exemple.com
http://
https://
invalid_api_key
Refused to load the script
script-src
connect-src
z-index: 2147483000
rendered_at
Les codes d'erreur complets sont dans le tableau du README.