Tout ce qu'il faut pour embarquer un sondage, vérifier sa disponibilité et transmettre des soumissions. Aucun SDK requis — deux endpoints et un URL.
Le SDK Flutter (insightdive_sdk) est l'intégration recommandée pour les apps mobiles. Il embarque le sondage dans un webview et diffuse les événements de cycle de vie. Deux modes d'intégration sont disponibles :
Le SDK gère la sheet — il l'ouvre, traite les événements et la ferme automatiquement après soumission. Configuration minimale :
Insightdive.configure( tenant: 'acme', survey: 'onboarding', apiKey: 'ik_…', productVersion: '2026.5.1', context: { // optionnel — voir Champs contextuels ↓ 'plan': 'enterprise', 'trial_days_remaining': 12, 'beta_user': true, }, ); // partout où un BuildContext est disponible : final result = await Insightdive.show(context); if (result.status == FeedbackStatus.completed) { // utilisateur a soumis }
Pose InsightdiveSurvey n'importe où dans ton arbre — une page plein écran, un onglet, une carte dans une liste. Tu contrôles quand l'afficher et le retirer :
if (_showSurvey) InsightdiveSurvey( options: Insightdive.options, onEvent: (event) { if (event is FeedbackCompleted) { setState(() => _showSurvey = false); } }, ),
Les événements du widget inline ne sont pas automatiquement transmis à Insightdive.events. Pour le faire, appelle Insightdive.addEvent(e) dans onEvent.
Avant de monter InsightdiveSurvey ou d'appeler show(), utilise isAvailable() pour confirmer que le sondage est actif. Évite d'afficher un écran "non publié" aux utilisateurs finaux :
Insightdive.isAvailable().then((active) {
if (active) setState(() => _showSurvey = true);
});
Appelle GET /api/v1/surveys/{survey}/status — public, sans auth, mis en cache 30 s. Retourne false en cas d'erreur réseau pour que ton point d'entrée reste caché plutôt que de planter.
Installe @insightdive/sdk depuis npm :
npm install @insightdive/sdk
Intégration modale :
import { Insightdive } from '@insightdive/sdk';
Insightdive.configure({
tenant: 'acme',
survey: 'onboarding',
apiKey: 'ik_abc123...',
context: { // optionnel — voir Champs contextuels ↓
plan: 'enterprise',
trialDaysRemaining: 12,
betaUser: true,
},
});
const available = await Insightdive.isAvailable();
if (available) {
const result = await Insightdive.show();
// result.status === 'completed' | 'dismissed'
}
Widget inline :
// le container doit avoir une hauteur explicite en CSS
const container = document.getElementById('survey-slot');
Insightdive.embed(container);
Événements du cycle de vie :
Insightdive.on('completed', (e) => {
analytics.track('feedback_completed', { id: e.submissionId });
});
Insightdive.on('dismissed', () => {
analytics.track('feedback_dismissed');
});
Référence complète : Page intégrations · npm.
Le SDK .NET (Insightdive) est l'intégration recommandée pour les applications desktop Avalonia. Le sondage s'ouvre dans un AvaloniaWebView à l'intérieur d'une fenêtre 460×680 — jamais plein écran, jamais bloquant. Deux cibles sont incluses dans le même package :
| Cible | Notes |
|---|---|
net8.0 | Interface Avalonia complète — dialog modale, contrôle inline, capture d'écran optionnelle |
netstandard2.0 | API de vérification uniquement — sans dépendance UI, pour les backends ou projets non-Avalonia |
dotnet add package Insightdive
Appelle InsightdiveSDK.Configure() une seule fois au démarrage de l'application, avant l'affichage de toute fenêtre :
InsightdiveSDK.Configure(new InsightdiveOptions { Tenant = "acme", Survey = "nps-q1", ApiKey = "ik_…", // Admin → Paramètres → API ProductVersion = Assembly.GetEntryAssembly() ?.GetName().Version?.ToString(), ProductIdentifier = "myapp-desktop", Locale = CultureInfo.CurrentCulture.Name, Theme = "dark", // "light" | "dark" Context = new Dictionary<string, object> // optionnel — voir Champs contextuels ↓ { ["plan"] = "enterprise", ["trial_days_remaining"] = 12, ["beta_user"] = true, }, });
Appelle IsAvailableAsync() avant ShowAsync() pour confirmer que le sondage est actif et que le cooldown est écoulé. En cas d'erreur réseau, IsAvailableAsync retourne true (fail-open) pour qu'une panne passagère ne masque pas définitivement le sondage :
if (await InsightdiveSDK.Instance.IsAvailableAsync()) { var result = await InsightdiveSDK.Instance.ShowAsync(this); if (result?.Status == FeedbackStatus.Completed) Console.WriteLine($"Soumis : {result.SubmissionId}"); }
Utilise TriggerAsync(nomEvénement) pour faire correspondre un événement SDK nommé à la liste Événements déclencheurs du sondage (Admin → Insight → Paramètres → Livraison). Si l'événement correspond — ou si la liste est vide — la méthode retourne true :
// S'affiche uniquement quand "onboarding_completed" correspond à la liste du sondage if (await InsightdiveSDK.Instance.TriggerAsync("onboarding_completed")) await InsightdiveSDK.Instance.ShowAsync(this);
Souscris à FeedbackEventOccurred pour réagir à chaque étape de la session de sondage :
InsightdiveSDK.Instance.FeedbackEventOccurred += (_, e) =>
{
switch (e)
{
case FeedbackViewedEvent v:
analytics.Track("survey_viewed", v.SessionId); break;
case FeedbackCompletedEvent c:
analytics.Track("survey_completed", c.SubmissionId); break;
case FeedbackDismissedEvent d:
analytics.Track("survey_dismissed"); break;
}
};
Intègre le sondage directement dans ta mise en page au lieu d'une dialog. Utilise InsightdiveSurveyControl du namespace Insightdive.Avalonia_ :
using Insightdive.Avalonia_; var opts = InsightdiveSDK.Instance.Options; var token = await InsightdiveSDK.Instance.FetchEmbedTokenAsync(); var url = UrlBuilder.SurveyUrl(opts, token); var control = new InsightdiveSurveyControl(InsightdiveSDK.Instance, url); control.EventOccurred += (_, e) => { /* gérer les événements */ }; MonPanneau.Children.Add(control);
La cible netstandard2.0 expose uniquement l'API de disponibilité sans dépendance UI — utile pour du code serveur ou des projets non-Avalonia qui ont seulement besoin de savoir si un sondage est actif :
InsightdiveSDK.Configure(new InsightdiveOptions { Tenant = "acme", Survey = "nps-q1", ApiKey = "ik_…" }); bool disponible = await InsightdiveSDK.Instance.IsAvailableAsync(); bool déclenché = await InsightdiveSDK.Instance.TriggerAsync("app_launched");
Référence complète des options : Page intégrations · NuGet.
Les trois SDKs acceptent un objet context optionnel lors de configure(). Le contexte est transmis à chaque soumission et stocké en JSON opaque aux côtés des réponses. Les admins peuvent visualiser les valeurs dans le détail d'une réponse, filtrer les réponses par clé de contexte et les exporter sous forme de colonnes context.* dans les exports CSV.
Anonyme by design — Insightdive stocke le blob de contexte tel quel, sans en interpréter les clés. Ne mets jamais d'identifiants utilisateurs, d'emails ou d'autres données personnelles dans les champs de contexte. Si tu as besoin de corréler des réponses avec tes utilisateurs, utilise plutôt le pattern Respondent Token.
| Contrainte | Limite |
|---|---|
| Clés max | 20 |
| Format des clés | [a-z0-9_], max 64 chars |
| Types de valeurs | string (max 255 chars), number ou boolean |
| Taille JSON totale | 4 Ko |
Les clés ou valeurs invalides sont ignorées silencieusement — la soumission n'est jamais rejetée à cause de métadonnées de contexte incorrectes.
// Segmenter par plan d'abonnement context: { plan: 'enterprise', trial_days_remaining: 12 } // Contexte de feature flags context: { feature_vault_v2: true, feature_export: false } // Contexte de surface / flux context: { surface: 'onboarding', step: 'invite_team' }
Un token répondant est une chaîne opaque générée par ton système et transmise au SDK lors de configure(). Insightdive le stocke tel quel aux côtés de la soumission — il n'est jamais décodé, jamais exposé aux répondants et jamais utilisé pour identifier quiconque. Il permet à ton équipe de corréler une réponse avec tes propres enregistrements internes sans envoyer un identifiant PII à Insightdive.
L'approche recommandée est de hasher l'identifiant utilisateur avec un secret côté serveur (sel de workspace) pour que le token soit opaque pour Insightdive tout en restant recalculable de ton côté pour retrouver toutes les réponses d'un utilisateur :
import { createHmac } from 'node:crypto'; // WORKSPACE_SALT est un secret de workspace stocké côté serveur — jamais exposé au client. const token = createHmac('sha256', process.env.WORKSPACE_SALT) .update(userId) .digest('hex'); Insightdive.configure({ tenant: 'acme', survey: 'nps-q1', apiKey: '...', respondentToken: token, // opaque pour Insightdive, recalculable par toi });
import 'dart:convert'; import 'package:crypto/crypto.dart'; final key = utf8.encode(workspaceSalt); // fourni par le serveur, jamais dans l'app final bytes = utf8.encode(userId); final token = Hmac(sha256, key).convert(bytes).toString(); Insightdive.configure( tenant: 'acme', survey: 'nps-q1', apiKey: '...', respondentToken: token, );
using System.Security.Cryptography; using System.Text; var keyBytes = Encoding.UTF8.GetBytes(WorkspaceSalt); // secret côté serveur var msgBytes = Encoding.UTF8.GetBytes(userId); using var hmac = new HMACSHA256(keyBytes); var token = Convert.ToHexString(hmac.ComputeHash(msgBytes)).ToLower(); InsightdiveSDK.Configure(new InsightdiveOptions { Tenant = "acme", Survey = "nps-q1", ApiKey = "...", RespondentToken = token, });
| Contrainte | Limite |
|---|---|
| Longueur max | 128 caractères |
| Caractères autorisés | [a-zA-Z0-9-_] uniquement |
| Indexé | Oui — filtre exact-match dans l'admin et l'API |
Les tokens invalides sont ignorés silencieusement — la soumission n'est jamais rejetée à cause d'un token incorrect.
a3f2c1d8…e9b1. Elle ne peut pas être retransformée en identifiant utilisateur.Le contact opt-in est une fonctionnalité initiée par l'utilisateur et relayée par l'opérateur. Quand elle est activée, l'éditeur de sondages permet d'ajouter une question de type contact_opt_in. Le répondant voit une case à cocher (« Je suis ouvert(e) à un suivi ») et un champ de saisie. S'il consent, Insightdive :
Anonyme by design : la valeur de contact n'est jamais affichée en clair dans l'interface admin d'Insightdive. L'admin affiche uniquement le statut de consentement, le nom du champ et le statut de livraison webhook. Insightdive joue le rôle de relais transitoire, pas de carnet de contacts.
Le contact opt-in est désactivé par défaut. Active-le dans ton workspace sous Paramètres → Contact Opt-in. Une fois activé, un nouveau type de question contact_opt_in apparaît dans l'éditeur.
Dans l'éditeur, ajoute une question contact_opt_in. Configure :
email | phone | ticket_refLa question contact opt-in est toujours optionnelle pour le répondant (il peut décocher et soumettre sans fournir d'info de contact).
Quand une soumission inclut une réponse opt-in, le payload du webhook submission.created contient un champ contactOptIn :
{
"event": "submission.created",
"data": {
"submissionId": "clxxx...",
"projectName": "Onboarding NPS",
"receivedAt": "2026-05-15T14:32:00.000Z",
"preview": "Recommanderiez-vous → 9",
"contactOptIn": {
"consented": true,
"fieldName": "email",
"value": "user@example.com"
}
}
}
Quand le répondant n'a pas consenti, consented est false et value est null. Quand le sondage ne contient pas de question opt-in, contactOptIn est null.
| Donnée | Stockée par Insightdive | TTL |
|---|---|---|
| Statut de consentement | Oui (toujours) | Avec la soumission (pas de TTL) |
| Nom du champ | Oui (pas de données perso) | Avec la soumission (pas de TTL) |
| Valeur de contact | Chiffrée AES-256-GCM | 7 jours (supprimée après livraison webhook) |
| Valeur en clair | Jamais stockée | — |
Si ton endpoint webhook est inaccessible plus de 7 jours, les données chiffrées sont purgées et la valeur de contact est définitivement perdue. Veille à maintenir ton webhook opérationnel.
Insightdive est basé sur des URLs : le sondage vit à un URL public que tu ouvres dans une popup. L'app embarquante n'a que deux choses à faire :
GET /api/v1/surveys/{slug}/status. Si enabled est false, masque le bouton silencieusement — l'utilisateur ne voit jamais de page d'erreur./s/{slug} avec les query params requis dans une petite popup (bottom sheet sur mobile, fenêtre ~460×680 sur desktop). Toute la logique du sondage — questions, suivi AI, soumission — tourne à l'intérieur.Aucun SDK n'est requis côté client. Les soumissions sont anonymes par défaut : seuls productIdentifier, productVersion et les réponses sont collectés. Les opérateurs peuvent optionnellement enrichir avec des champs context ou un respondentToken pseudonyme. Aucun userId, email ou adresse IP n'est jamais collecté.
L'endpoint status est public — aucune authentification n'est nécessaire. Tous les endpoints qui lisent ou écrivent des données privées requièrent une clé API workspace envoyée comme bearer token :
Authorization: Bearer <ta-clé-api>
Trouve ta clé API dans ton workspace sous Paramètres → API. Elle est scopée à ton tenant — ne l'expose jamais dans du code côté client.
Vérifie si un sondage est actif et prêt à être affiché. Renvoie toujours 200 — tu branches uniquement sur le booléen enabled. Toute erreur réseau ou réponse non-200 doit être traitée comme enabled: false pour que le bouton reste masqué.
{
"slug": "mon-projet",
"enabled": true,
"name": "Mon projet",
"url": "https://<tenant>.insightdive.com/s/mon-projet",
"acceptsLocale": true, // passe ?locale= si true
"acceptsTheme": true // passe ?theme= si true
}
// reason: "disabled" | "not_published" | "not_found" { "slug": "mon-projet", "enabled": false, "reason": "disabled" }
Quand enabled est true, ouvre cet URL dans une popup discrète — jamais plein écran. Sur mobile, un bottom sheet à ~75% de hauteur. Sur desktop, un window.open à 460×680 px convient bien.
https://<tenant>.insightdive.com/s/mon-projet ?productIdentifier=myapp-desktop # requis &productVersion=2026.5.1 # requis &locale=fr # optionnel — uniquement si acceptsLocale: true &theme=dark # optionnel — uniquement si acceptsTheme: true
| Paramètre | Description |
|---|---|
| productIdentifierrequis | Identifie quel produit embarqué est à l'origine du feedback (ex : myapp-desktop, myapp-mobile). Permet de filtrer les réponses par surface dans l'admin quand plusieurs produits partagent le même sondage. |
| productVersionrequis | Version du build du produit embarqué (ex : 2026.5.1). Stocké avec chaque réponse pour un filtrage par version. |
| localeoptionnel | Code de langue à passer au sondage (ex : fr, en). Ignoré si acceptsLocale est false dans la réponse status. |
| themeoptionnel | Thème visuel : light ou dark. Ignoré si acceptsTheme est false dans la réponse status. |
Crée une soumission directement. Réservé aux intégrations server-to-server — backends qui collectent déjà du feedback et veulent le transmettre à Insightdive. Les clients côté utilisateur doivent utiliser l'URL /s/{slug} à la place, qui gère automatiquement l'interface du sondage et la soumission.
{
"schemaVersion": 1, // optionnel, actuellement 1
"projectId": "<project-id>", // requis — visible dans Admin → Projet
"productIdentifier": "myapp-desktop", // recommandé
"productVersion": "2026.5.1", // recommandé
"context": { // optionnel — voir Champs contextuels ↑
"plan": "enterprise",
"trial_days_remaining": 12
},
"respondentToken": "a3f2...e9b1", // optionnel — voir Token répondant ↑
"summary": "J'adore le nouveau sélecteur de vault.",
"transcript": [
{
"questionId": "q1",
"questionText": "Comment l'évaluez-vous ?",
"type": "number",
"value": "9"
}
]
}
{
"id": "clxxxxxxxxxxxxxxxxxxxxxxx",
"receivedAt": "2026-05-15T14:32:00.000Z",
"status": "new"
}
Retourne les soumissions du workspace authentifié, de la plus récente à la plus ancienne. Limité à 100 par appel.
| Query param | Description |
|---|---|
| projectIdoptionnel | Filtre sur un projet spécifique par ID. |
| statusoptionnel | Filtre par statut : new | reviewed | archived |
| limitoptionnel | Nombre maximum de résultats (1–100, défaut 20). |
{
"count": 2,
"submissions": [
{
"id": "clxxxxxxxxxxxxxxxxxxxxxxx",
"receivedAt": "2026-05-15T14:32:00.000Z",
"status": "new",
"productIdentifier": "myapp-desktop",
"productVersion": "2026.5.1",
"summary": "J'adore le nouveau sélecteur de vault.",
"project": { "id": "...", "name": "Mon projet", "slug": "mon-projet" }
}
]
}
Tous les endpoints renvoient des erreurs JSON structurées. Les rate limits s'appliquent par adresse IP.
| Status | Erreur | Signification |
|---|---|---|
| 400 | Invalid body |
Le corps de la requête échoue la validation de schéma. Consulte le champ details pour le chemin exact. |
| 401 | Unauthorized |
Bearer token manquant ou invalide. |
| 402 | plan_limit_reached |
Le workspace a atteint sa limite mensuelle de soumissions. Passe à un plan supérieur ou attends le prochain cycle. |
| 403 | workspace_suspended |
Workspace suspendu. Contacte le support. |
| 404 | Project not found |
Le projectId n'existe pas dans ce workspace. |
| 429 | rate_limited |
Trop de requêtes. Consulte le header Retry-After (en secondes). Limites : 120 req/min sur /status, 60 req/min sur /submissions. |
Le guide d'intégration dans ton admin génère des snippets de code adaptés à ton slug de projet et à l'URL de ton tenant.
Créer ton workspace