Comment connecter plusieurs comptes Instagram à un seul webhook endpoint
13 mai 2026
·
15 min de lecture
Dans ce guide : Comment fonctionnent les webhooks Instagram multi-comptes · Le pattern de routage entry.id · Gestion des tokens par compte · Schéma de base de données pour le multi-comptes · Isolation des données clients · Gestion des erreurs et circuit breakers · Onboarding d'un nouveau compte client · SocialHook comme couche managée
Comment fonctionnent les webhooks Instagram multi-comptes sous le capot
Quand vous enregistrez une URL de webhook dans votre App Facebook et que vous l'abonnez aux événements Instagram, cette unique URL reçoit les événements de chaque Instagram Business Account qui a accordé à votre App la permission instagram_manage_messages. C'est conçu ainsi — l'architecture webhook de Meta est fondamentalement multi-tenant. Une App, une URL de webhook, un nombre illimité de comptes.
Le mécanisme : quand un client envoie un DM à n'importe quel compte Instagram connecté, Meta déclenche un HTTP POST vers votre URL de webhook. Le corps de la requête contient un tableau entry, où chaque élément représente les événements d'un compte. Le champ entry[0].id est l'Instagram Business Account ID (IGID) qui a généré l'événement.
Voici le tableau complet de ce dont vous avez besoin pour construire un système multi-comptes :
Une App Facebook avec un seul enregistrement de webhook (une URL, un verify token, un App Secret)
Un Page Access Token par compte Instagram — chaque token est spécifique à la Page Facebook liée à ce compte Instagram
Une table de routage qui mappe chaque Instagram Business Account ID à son Page Access Token, ses données client et son contexte de base de données
L'isolation des clients pour que les erreurs, les rate limits ou les données d'un client n'affectent jamais ceux d'un autre
Le champ entry.id : la clé de routage de tout
Chaque événement webhook Instagram que vous recevez — qu'il s'agisse d'un DM, d'une réponse à une story, d'une mention de story ou d'une réaction — contient l'Instagram Business Account ID dans entry[0].id. C'est le champ qui route l'événement vers le bon client. Stockez-le comme clé primaire dans votre table accounts. Faites une recherche dessus pour chaque événement entrant. Ne traitez jamais un événement sans avoir d'abord résolu à quel client il appartient.
Payload webhook brut — entry.id est votre clé de routage
{
"object": "instagram",
"entry": [{
"id": "987654321098765", // ← C'EST VOTRE CLÉ DE ROUTAGE// = Instagram Business Account ID du compte qui a reçu le DM// = le même ID utilisé dans POST /{this_id}/messages pour envoyer les réponses"time": 1747231892,
"messaging": [{
"sender": { "id": "12345678901234" }, // IGSID du client"recipient": { "id": "987654321098765" }, // identique à entry.id"message": { "text": "Hello!" }
}]
}]
}
// entry[0].id vous dit : lequel des comptes Instagram de votre client a reçu ce DM// Utilisez-le pour retrouver : Page Access Token, client_id, shard de base de données, handler
entry peut contenir plusieurs éléments dans un seul POST. Si deux comptes de vos clients reçoivent des DMs dans le même lot de livraison, Meta peut les regrouper dans un seul POST avec deux éléments dans le tableau entry. Bouclez toujours sur toutes les entries, pas seulement entry[0]. Chaque élément a son propre champ id pointant vers un compte Instagram différent.
Architecture : une URL, plusieurs clients
Architecture de routage des webhooks multi-comptes
IG_ID: 987654321
@ClientA Brand
IG_ID: 111222333
@ClientB Store
IG_ID: 444555666
@ClientC Fashion
IG_ID: 777888999
@ClientD Beauty
→→→
Une seule URL de webhook
yourserver.com/webhook
→→→
Route via entry[0].id
Handler Client A
Token A · DB shard A
Handler Client B
Token B · DB shard B
Handler Client C
Token C · DB shard C
Handler Client D
Token D · DB shard D
Gestion des tokens : un token par compte
Chaque Instagram Business Account nécessite son propre Page Access Token — généré pour la Page Facebook liée à ce compte. C'est ce token que vous utilisez pour appeler la Send API Instagram pour ce compte spécifique. Vous ne pouvez pas utiliser le token du Client A pour envoyer des messages depuis le compte du Client B.
Règles de stockage des tokens :
Chiffrement au repos. Les Page Access Tokens sont des identifiants sensibles. Stockez-les chiffrés dans votre base de données (AES-256), pas en clair. Déchiffrez-les uniquement quand un appel API en a besoin.
Ne jamais logger les tokens. Assurez-vous que votre handler de webhook, votre logger d'erreurs et votre pipeline analytics ne logguent jamais la valeur brute du token.
Rotation des tokens. Les Page Access Tokens longue durée n'expirent pas par défaut, mais faites-en la rotation périodiquement (chaque trimestre) et chaque fois qu'un client se désabonne. Révoquez immédiatement les tokens des comptes qui se déconnectent de votre plateforme.
Un token par compte, chargé au moment de la requête. Ne mettez pas les tokens en cache mémoire indéfiniment — chargez-les depuis la base de données au moment de la requête pour que la révocation prenne effet immédiatement.
Schéma de base de données pour la gestion multi-comptes
Trois tables couvrent le modèle de données multi-comptes complet. La table ig_accounts est la table de routage — consultée à chaque événement entrant. La table igsids stocke les identifiants utilisateurs scopés à chaque compte, garantissant une isolation totale entre les clients.
Table ig_accounts — le registre de routage
Colonne
Type
Notes
ig_business_id PK
VARCHAR(64)
entry[0].id du webhook — aussi paramètre de chemin dans la Send API. À stocker comme chaîne.
client_id FK
UUID
Référence la table clients. Chaque requête filtre sur ce champ.
encrypted_token
TEXT
Page Access Token chiffré en AES-256. À déchiffrer uniquement au moment de la requête.
ig_username
VARCHAR(64)
Label lisible. @handle du compte Instagram.
active
BOOLEAN
Passer à false quand un client se désabonne. Empêche le traitement d'événements provenant de comptes déconnectés.
created_at
TIMESTAMPTZ
Date de connexion du compte.
token_rotated_at
TIMESTAMPTZ
Suivre l'ancienneté du token pour appliquer la politique de rotation.
Table igsids — identifiants utilisateurs par compte
Colonne
Type
Notes
igsid PK part
VARCHAR(64)
Instagram-Scoped ID de l'utilisateur. Scopé à CE compte uniquement.
ig_business_id PK partFK
VARCHAR(64)
PK composite avec igsid. Le même utilisateur écrivant à deux clients = deux IGSIDs différents.
client_id FK
UUID
Dénormalisé pour des requêtes rapides scopées au client.
first_seen
TIMESTAMPTZ
Premier DM de cet IGSID vers ce compte.
last_message
TIMESTAMPTZ
Pour suivre la fenêtre de 24 heures par compte.
metadata
JSONB
Toute donnée spécifique au client (email, nom, Shopify customer ID, etc.)
Le handler de routage multi-comptes
Voici le handler POST du webhook qui traite correctement les événements d'un nombre quelconque de comptes Instagram connectés. Toutes les contraintes de production sont couvertes : plusieurs entries dans un POST, IDs de compte inconnus, comptes inactifs, et isolation propre du contexte par client.
Node.js — multi-account Instagram webhook router
multiAccountRouter.js
const { getAccountToken } = require('./tokenStore');
const { verifySignature } = require('./verifySignature');
app.post('/webhook', verifySignature, async (req, res) => {
res.sendStatus(200); // toujours acquitter immédiatementconst body = req.body;
if (body.object !== 'instagram') return;
// Traiter TOUTES les entries — Meta peut grouper plusieurs comptes dans un seul POSTfor (const entry of body.entry) {
const igBusinessId = entry.id; // ← LA CLÉ DE ROUTAGE// Charger la config du compte — résout token, client_id et statut actifconst account = await db.query(
'SELECT * FROM ig_accounts WHERE ig_business_id = $1 AND active = true',
[igBusinessId]
);
if (!account.rows[0]) {
// Compte inconnu ou déconnecté — logger et ignorer
console.warn(`Unknown/inactive IG account: ${igBusinessId}`);
continue;
}
const { client_id, encrypted_token, ig_username } = account.rows[0];
const pageToken = decryptToken(encrypted_token);
// Construire le contexte scopé au client — injecté dans tous les handlersconst ctx = {
igBusinessId, // utilisé pour le chemin de la Send API
clientId: client_id,
pageToken, // pour envoyer les réponses
accountName: ig_username,
sendDM: (igsid, text) => sendInstagramDM(igBusinessId, pageToken, igsid, text),
logEvent: (data) => analytics.log({ ...data, client_id, igBusinessId }),
};
// Traiter chaque événement de messagerie dans l'entry de ce comptefor (const event of (entry.messaging || [])) {
// Traiter dans un try/catch pour qu'une erreur d'un compte n'affecte pas les autrestry {
awaitprocessEvent(event, ctx);
} catch (err) {
// Logger avec le contexte client — ne jamais re-throw (ça sauterait les comptes restants)
console.error(`Error processing event for ${ig_username} [${igBusinessId}]:`, err.message);
await errorTracker.capture(err, { igBusinessId, client_id });
}
}
}
});
// Wrapper de la Send API conscient du contexte clientasync functionsendInstagramDM(igBusinessId, pageToken, igsid, text) {
const res = awaitfetch(
`https://graph.facebook.com/v21.0/${igBusinessId}/messages?access_token=${pageToken}`,
{ method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ recipient: { id: igsid }, message: { text } }) }
);
if (!res.ok) {
const err = await res.json();
thrownewError(`DM send failed [${igBusinessId}]: ${err.error?.message}`);
}
returnawait res.json();
}
Isolation des données clients : éviter la contamination croisée
Dans un système multi-tenant, le pire mode de défaillance est que les données d'un client apparaissent dans le contexte d'un autre. Chaque requête de base de données qui touche aux données utilisateur — historique de conversation, enregistrements IGSID, analytics — doit être filtrée par client_id ET ig_business_id.
Node.js — client-scoped database operations
clientDB.js
// ✗ Faux — pas de scope client, pourrait retourner les conversations de n'importe quel compteconst convos = await db.query('SELECT * FROM conversations WHERE igsid = $1', [igsid]);
// ✓ Correct — toujours scoper par ig_business_id ET client_idconst convos = await db.query(
'SELECT * FROM conversations WHERE igsid = $1 AND ig_business_id = $2 AND client_id = $3',
[igsid, ctx.igBusinessId, ctx.clientId]
);
// ─── Pattern : passer ctx partout, ne jamais passer d'IDs bruts ────────────────────// Upsert d'un enregistrement IGSID scopé à ce compteasync functionupsertUser(igsid, ctx) {
return db.query(`
INSERT INTO igsids (igsid, ig_business_id, client_id, first_seen, last_message)
VALUES ($1, $2, $3, NOW(), NOW())
ON CONFLICT (igsid, ig_business_id)
DO UPDATE SET last_message = NOW()
RETURNING *`,
[igsid, ctx.igBusinessId, ctx.clientId]
);
}
// Logger un message de conversation scopé à ce compteasync functionlogMessage(igsid, text, direction, ctx) {
return db.query(
'INSERT INTO messages (igsid, ig_business_id, client_id, text, direction, created_at) VALUES ($1,$2,$3,$4,$5,NOW())',
[igsid, ctx.igBusinessId, ctx.clientId, text, direction]
);
}
Gestion des erreurs et circuit breakers
Dans un système webhook mono-compte, une erreur peut remonter et arrêter tout le traitement. Dans un système multi-comptes, une erreur pour le Client A ne doit jamais empêcher le traitement des événements pour les Clients B, C et D. Deux patterns sont essentiels :
try/catch par compte (déjà montré ci-dessus) : Encadrez le traitement des événements de chaque compte dans son propre try/catch à l'intérieur de la boucle entry. Loggez les erreurs avec le contexte client. Ne re-throwez jamais — une erreur re-throwée à l'intérieur de la boucle interromprait toutes les entries restantes.
Circuit breaker par compte : Si un compte produit des erreurs répétées (token invalide, rate limit, permission de l'App révoquée), arrêtez temporairement de traiter les événements de ce compte plutôt que de marteler un endpoint cassé à chaque événement.
Node.js — per-account circuit breaker
circuitBreaker.js
const errorCounts = newMap(); // igBusinessId → { count, firstErrorAt }constCIRCUIT_THRESHOLD = 5; // ouvre le circuit après 5 erreursconstRESET_AFTER_MS = 10 * 60 * 1000; // reset après 10 minutesfunctionisCircuitOpen(igBusinessId) {
const state = errorCounts.get(igBusinessId);
if (!state) returnfalse;
// Auto-reset après RESET_AFTER_MSif (Date.now() - state.firstErrorAt > RESET_AFTER_MS) {
errorCounts.delete(igBusinessId);
returnfalse;
}
return state.count >= CIRCUIT_THRESHOLD;
}
functionrecordError(igBusinessId) {
const state = errorCounts.get(igBusinessId) || { count: 0, firstErrorAt: Date.now() };
state.count++;
errorCounts.set(igBusinessId, state);
if (state.count === CIRCUIT_THRESHOLD) {
console.error(`⚡ Circuit opened for ${igBusinessId} after ${CIRCUIT_THRESHOLD} errors`);
// Alertez votre équipe ops via Slack/PagerDuty ici
}
}
// Dans la boucle par compte du router :if (isCircuitOpen(igBusinessId)) {
console.warn(`Circuit open for ${igBusinessId} — skipping`);
continue;
}
try {
awaitprocessEvent(event, ctx);
} catch (err) {
recordError(igBusinessId);
console.error(`Error for ${igBusinessId}:`, err.message);
}
Onboarding d'un nouveau compte client
Chaque fois qu'un nouveau client connecte son compte Instagram à votre plateforme, vous devez exécuter une séquence d'étapes précise. Construire ça comme un flow d'onboarding formel (plutôt que des inserts manuels en base) rend le passage à l'échelle vers de nombreux clients gérable :
OAuth ou collecte manuelle du token : Soit vous implémentez l'OAuth Facebook (pour du self-serve), soit vous générez manuellement un Page Access Token dans le Meta Developer Portal. Le token doit inclure les permissions instagram_manage_messages, pages_messaging et pages_read_engagement.
Résoudre l'Instagram Business Account ID : Appelez GET /me?fields=instagram_business_account avec le Page Access Token. L'instagram_business_account.id dans la réponse est la valeur que vous stockez comme ig_business_id dans votre table accounts.
Vérifier que le token a l'accès messagerie : Testez avec un appel GET /{ig_business_id}?fields=id,name. S'il réussit, le token a les permissions nécessaires.
Insérer dans votre table accounts : Stockez le token chiffré, le client_id et l'ig_business_id. Mettez active = true.
Votre webhook reçoit déjà leurs événements. Aucun ré-enregistrement requis. L'abonnement webhook existant sur votre App Facebook commence automatiquement à livrer les événements de tout compte qui accorde des permissions à votre App. La recherche en base de votre router prend en charge le nouveau compte automatiquement.
Node.js — client onboarding sequence
onboarding.js
async functiononboardInstagramAccount(clientId, pageAccessToken) {
// Étape 1 : Résoudre l'Instagram Business Account ID depuis le tokenconst meRes = awaitfetch(
`https://graph.facebook.com/v21.0/me?fields=instagram_business_account&access_token=${pageAccessToken}`
);
const me = await meRes.json();
if (!me.instagram_business_account?.id) {
thrownewError('No Instagram Business Account linked to this Page token');
}
const igBusinessId = me.instagram_business_account.id;
// Étape 2 : Récupérer le username du compte pour un label lisibleconst igRes = awaitfetch(
`https://graph.facebook.com/v21.0/${igBusinessId}?fields=username&access_token=${pageAccessToken}`
);
const igAccount = await igRes.json();
// Étape 3 : Chiffrer et stocker le tokenconst encryptedToken = encryptToken(pageAccessToken);
await db.query(`
INSERT INTO ig_accounts (ig_business_id, client_id, encrypted_token, ig_username, active, created_at, token_rotated_at)
VALUES ($1, $2, $3, $4, true, NOW(), NOW())
ON CONFLICT (ig_business_id) DO UPDATE
SET encrypted_token = $3, client_id = $2, active = true, token_rotated_at = NOW()
RETURNING *`,
[igBusinessId, clientId, encryptedToken, igAccount.username || igBusinessId]
);
// Étape 4 : Pas de ré-enregistrement de webhook nécessaire — votre abonnement webhook existant// sur votre App Facebook couvre automatiquement les événements de ce compte.// La recherche en DB du router (ig_accounts WHERE ig_business_id = $1) s'en charge.
console.log(`✓ Onboarded @${igAccount.username} [${igBusinessId}] for client ${clientId}`);
return { igBusinessId, username: igAccount.username };
}
SocialHook : la couche multi-comptes managée
Construire et maintenir un router webhook multi-comptes — avec chiffrement, circuit breakers, isolation des clients et flows d'onboarding — représente des semaines de travail d'infrastructure qui ne sont pas votre cœur de produit. SocialHook gère tout cela en tant qu'infrastructure managée.
Connectez tous les comptes Instagram de vos clients à un seul workspace SocialHook. Les événements de chaque compte client arrivent à votre webhook endpoint unique dans le format normalisé, avec le contexte client déjà résolu :
SocialHook — multi-account events already routed
// Chaque événement de n'importe quel compte connecté :
{
"platform": "instagram",
"event": "message.received",
"ig_business_id": "987654321098765", // ← déjà extrait"account_name": "clientA_brand", // ← label lisible"from": "12345678901234", // ← IGSID"message": { "type": "text", "body": "Hello!" },
"signature_verified": true
}
// Votre handler utilise ig_business_id pour le routage — aucun lookup DB nécessaire
app.post('/webhook', express.json(), async (req, res) => {
res.sendStatus(200);
const { ig_business_id, account_name, from, message } = req.body;
// Router vers le handler spécifique au client via ig_business_idawait handlers[ig_business_id]?.handle(from, message);
// Ou plus simplement : utilisez ig_business_id comme clé de partition client dans votre DB
});
FAQ
Questions fréquentes
Une seule URL de webhook Instagram peut-elle recevoir les événements de plusieurs comptes ?
Oui. L'architecture webhook de Meta est multi-tenant par conception. Un seul enregistrement d'App Facebook livre les événements de tous les comptes Instagram qui ont accordé à votre App la permission instagram_manage_messages. Les événements de différents comptes arrivent dans le même corps de POST, différenciés par entry[0].id — qui est l'Instagram Business Account ID. Votre router lit ce champ et dispatche vers le bon handler client.
Comment identifier quel compte Instagram a envoyé un événement webhook ?
Vérifiez entry[0].id dans le payload webhook. C'est l'Instagram Business Account ID (IGID) qui a reçu le DM. C'est aussi l'ID numérique que vous utilisez comme paramètre de chemin quand vous appelez la Send API : POST /{entry[0].id}/messages. Stockez un mapping de cet ID vers le Page Access Token de votre client, le client_id et le contexte de base de données. Recherchez-le à chaque événement entrant.
Faut-il ré-enregistrer mon webhook quand j'ajoute un nouveau compte Instagram ?
Non. Votre enregistrement de webhook sur votre App Facebook est statique — il ne change pas par compte. Quand un nouveau compte Instagram accorde à votre App la permission instagram_manage_messages, ses événements commencent automatiquement à arriver sur votre URL de webhook enregistrée. Il vous suffit d'insérer ses identifiants dans votre table de routage accounts. Votre router existant gère le reste via la recherche sur entry[0].id.
entry peut-il contenir plusieurs comptes dans un seul POST ?
Oui. Si plusieurs comptes connectés reçoivent des messages dans le même lot de livraison, Meta peut les regrouper dans un seul POST avec plusieurs éléments dans le tableau entry. Bouclez toujours sur toutes les entries — for (const entry of body.entry) — pas seulement sur entry[0]. Chaque élément a son propre id pointant vers un Instagram Business Account différent.
Comment empêcher les erreurs d'un client d'affecter les autres clients ?
Encadrez le traitement des événements de chaque compte dans son propre try/catch à l'intérieur de la boucle entry. Loggez l'erreur avec le contexte client. Ne re-throwez jamais — une erreur re-throwée dans la boucle interrompt toutes les entries restantes. Pour les erreurs répétées sur un compte, implémentez un circuit breaker par compte qui ignore temporairement ce compte. Les deux patterns sont montrés dans la section gestion des erreurs ci-dessus.
Quel est le schéma de base de données correct pour les données webhook Instagram multi-comptes ?
Trois tables clés : clients (une ligne par client business), ig_accounts (une ligne par compte Instagram, avec Page Access Token chiffré et ig_business_id comme clé primaire), et igsids (clé primaire composite igsid + ig_business_id — ce qui garantit l'isolation des espaces de noms IGSID entre les comptes). Chaque requête sur les données utilisateur doit filtrer à la fois par ig_business_id ET client_id.
Tous les comptes clients. Un seul webhook. Zéro code de routage.
Connectez chaque compte Instagram de vos clients à SocialHook. Les événements arrivent sur votre endpoint pré-routés par compte, vérifiés HMAC, normalisés. Ajoutez un nouveau compte client en quelques minutes — aucun changement de code, aucun ré-enregistrement, aucune nouvelle URL de webhook. 50 $/mois quel que soit le nombre de comptes que vous gérez.
Arrêtez de gérer les API Meta. Commencez à construire.
Connectez votre premier compte Facebook, Instagram ou WhatsApp en moins de 2 minutes. Votre webhook reçoit son premier payload avant que votre café refroidisse.