Recalled
Les logs d'audit en SaaS pour les produits B2B et B2C.
Recalled stocke "qui a fait quoi, quand, depuis où" pour chaque action de tes utilisateurs, et te donne un log signé, cherchable et exportable à montrer à tes clients, tes auditeurs et ton équipe juridique.
Pourquoi
- Prêt pour la conformité : SOC 2, ISO 27001, RGPD exigent un audit trail. Recalled t'en livre un clé en main avec hébergement UE, chiffrement AES-256 au repos et une hash chain cryptographique.
- Ne pollue pas ta base : les logs d'audit grossissent vite et ralentissent tes requêtes en prod. Recalled stocke les events chez lui, indexés pour la recherche, avec rétention configurable.
- UI embeddable : un widget admin interne (composant React) pour tes ingés support, ton équipe ops, tes SRE et tes reviewers compliance. Branche-le dans le back-office que ton équipe utilise déjà pour opérer le produit, ils parcourent "qui a fait quoi" sans quitter leur workflow.
Comment ça marche
- Crée un projet dans le dashboard.
- Génère une clé API.
- Installe le SDK npm ou appelle l'API REST depuis n'importe quel langage.
- Envoie des events :
client.events.create({ action, actor, targets, metadata }). - Relis-les via le dashboard, l'API, ou le composant embeddable.
Livre ton premier event en 2 minutes
import { Recalled } from "@recalled/sdk";
const client = new Recalled({
apiKey: process.env.RECALLED_API_KEY!,
});
await client.events.create({
action: "invoice.deleted",
actor: { id: "user_123", email: "alice@example.com" },
organization: "org_abc",
targets: [{ type: "invoice", id: "inv_42" }],
metadata: { reason: "duplicate" },
});C'est fait. Tu logges.
Setup avec un LLM
Si tu intègres Recalled avec l'aide de Claude, Cursor, ChatGPT ou n'importe quel assistant IA de code, colle le prompt ci-dessous dans le contexte de l'assistant en premier. Il lui dit les règles de quoi logger, quoi skipper, comment nommer les actions, et quoi mettre dans metadata, pour qu'il arrête de te poser 50 questions et livre une intégration propre du premier coup.
Tu intègres Recalled (audit logs as a service) dans une app existante.
# Règles
1. Logue les changements d'état, pas les lectures. Un user qui lit un dashboard 50 fois n'est pas un audit event. Un user qui change son email, oui.
2. Logue les actions à conséquences, pas le bruit technique. Health checks, cache miss, réponses 304 : pas ici. Ça va dans l'APM.
3. Logue ce qui raconte une histoire. Dans 6 mois quelqu'un va demander "qui a fait ça et quand". La réponse doit venir d'un seul event avec actor, target, raison, IP, moment.
# Quoi logger
Authentification : user.signed_up, user.logged_in, user.logged_out, user.login_failed, user.password_changed, user.password_reset_requested, user.password_reset_completed, user.email_changed, user.two_factor_enabled, user.two_factor_disabled, user.session_revoked, magic_link.sent, magic_link.consumed, oauth.linked, oauth.unlinked.
Autorisation : member.invited, member.joined, member.removed, member.role_changed, team.created, team.deleted, permission.granted, permission.revoked, api_key.created, api_key.revoked, sharing.granted, sharing.revoked, ownership.transferred.
Cycle de vie sur chaque objet métier (invoice, project, document, etc.) : <objet>.created, <objet>.updated, <objet>.deleted, <objet>.archived, <objet>.restored, <objet>.published, <objet>.unpublished, <objet>.duplicated, <objet>.moved.
Argent : subscription.created, subscription.updated, subscription.canceled, subscription.plan_changed, invoice.created, invoice.paid, invoice.failed, invoice.refunded, payment.succeeded, payment.failed, refund.issued, refund.completed, coupon.applied, coupon.expired, payment_method.added, payment_method.removed, payment_method.set_default.
Actions admin (toujours, sans exception) : admin.impersonation_started, admin.impersonation_ended, admin.user_unlocked, admin.user_locked, admin.feature_toggled, admin.data_overridden, admin.support_intervention.
Exports et imports : export.started, export.completed, export.failed, import.started, import.completed, import.failed, bulk_delete.requested, bulk_delete.completed, gdpr.access_request, gdpr.erasure_request.
Intégrations : integration.connected, integration.disconnected, webhook.created, webhook.updated, webhook.deleted, webhook.delivery_failed (uniquement après que les retries soient épuisés).
Sécurité : security.brute_force_detected, security.suspicious_login, security.rate_limit_exceeded (uniquement si persistant), security.csp_violation_reported, security.api_key_leaked.
Système et jobs background (uniquement quand significatif) : cron.<nom>.completed, cron.<nom>.failed (un par run, pas par item), migration.applied, migration.rolled_back, backup.created, backup.restored.
# Quoi skipper
- Requêtes GET, page views, lectures de dashboard, scroll
- Auto-saves et drafts si sauvegardés toutes les quelques secondes
- Refresh de token, health checks, vérifs CSRF
- Vérifs de permissions (chaque requête API en fait)
- Chaque livraison de webhook réussie, chaque itération de batch, chaque invalidation de cache
- Heartbeats et liveness probes
- Tout ce qui dépasse ~2 KB en metadata
# Convention de nommage
Format : <domaine>.<sujet>.<verbe_passé>, tout en minuscules, séparé par points, snake_case dans un segment si nécessaire. Verbes au passé toujours (.created pas .create, .deleted pas .delete). Sois cohérent : ne mélange pas .deleted et .removed pour le même domaine.
# Metadata
À toujours inclure quand pertinent : source (web, mobile, api, admin_panel, automation, import, webhook), reason (texte libre si l'utilisateur en a fourni une), request_id (id de corrélation), result (success ou failure).
Pour les updates : tableau changed_fields avec les noms de champs, plus before/after uniquement pour les petits diffs.
Pour l'argent : amount_cents (entier, jamais float), currency, provider id (stripe_payment_intent_id ou équivalent).
Pour les échecs : result: "failure", reason, code.
# Antipatterns
Ne jamais mettre dans metadata : secrets en clair, mots de passe, tokens complets, numéros de carte complets, corps de documents complets, contenu de fichiers, blobs, PII non nécessaire, stack traces, requêtes SQL, quoi que ce soit de plus de ~2 KB.
# Comment ajouter l'appel SDK
Utilise @recalled/sdk :
import { Recalled } from "@recalled/sdk";
const client = new Recalled({ apiKey: process.env.RECALLED_API_KEY });
client.events.emit({ action: "...", actor: {...}, organization: "...", targets: [...], metadata: {...} });
Utilise emit() (résilient, non bloquant) par défaut. Utilise create() (throw en cas d'échec) uniquement quand le log d'audit fait partie de la condition de succès de la requête.
Maintenant parcours la codebase, trouve les endroits qui matchent le catalogue ci-dessus, et ajoute les appels client.events.emit appropriés. Skip tout ce qui ne matche pas.Pour le guide opinion complet sur quoi logger, voir Quoi logger. Le serveur MCP expose aussi ça comme un tool : get_setup_guide retourne le même prompt pour que les agents IA puissent le lire à la demande.