كيفية ربط عدة حسابات Instagram بنقطة نهاية Webhook واحدة
13 مايو 2026
·
15 دقيقة قراءة
في هذا الدليل: كيف تعمل Webhooks متعددة الحسابات على Instagram · نمط التوجيه عبر entry.id · إدارة الرموز لكل حساب · مخطط قاعدة البيانات لتعدد الحسابات · عزل بيانات العملاء · معالجة الأخطاء وقواطع الدائرة · إعداد حساب عميل جديد · SocialHook بوصفها الطبقة المُدارة
كيف تعمل Webhooks متعددة الحسابات على Instagram تحت الغطاء
عندما تُسجّل عنوان URL لـ webhook في تطبيق Facebook الخاص بك وتشترك في أحداث Instagram، يستقبل هذا العنوان الواحد الأحداثَ من كل حساب Instagram Business منح تطبيقك صلاحية instagram_manage_messages. هذا تصميم مقصود — معمارية webhook من Meta متعددة المستأجرين جوهرياً. تطبيق واحد، عنوان webhook واحد، عدد لا محدود من الحسابات.
الآلية: عندما يُرسل عميل DM إلى أي حساب Instagram متصل، تُطلق Meta طلب HTTP POST إلى عنوان webhook الخاص بك. يحتوي جسم الطلب على مصفوفة entry، حيث يمثّل كل عنصر فيها أحداث حساب واحد. حقل entry[0].id هو معرّف حساب Instagram Business (IGID) الذي ولّد الحدث.
هذه هي الصورة الكاملة لما تحتاجه لبناء نظام متعدد الحسابات:
تطبيق Facebook واحد بتسجيل webhook واحد (عنوان واحد، رمز تحقق واحد، App Secret واحد)
Page Access Token واحد لكل حساب Instagram — كل رمز خاص بصفحة Facebook المرتبطة بهذا الحساب
جدول توجيه يربط كل معرّف Instagram Business Account برمز Page Access Token الخاص به وبيانات العميل وسياق قاعدة البيانات
عزل العملاء بحيث لا تؤثر أبداً أخطاء أحد العملاء أو حدود معدلاته أو بياناته على غيره
حقل entry.id: مفتاح التوجيه لكل شيء
كل حدث Instagram webhook تستقبله — سواء كان DM أو رد ستوري أو إشارة في ستوري أو تفاعل — يحتوي على معرّف حساب Instagram Business عند entry[0].id. هذا هو الحقل الذي يوجّه الحدث إلى العميل الصحيح. خزّنه كمفتاح أساسي في جدول الحسابات. ابحث عنه مع كل حدث وارد. لا تعالج حدثاً أبداً قبل تحديد العميل الذي ينتمي إليه.
Raw webhook payload — entry.id is your routing key
{
"object": "instagram",
"entry": [{
"id": "987654321098765", // ← THIS IS YOUR ROUTING KEY// = Instagram Business Account ID of the account that got the DM// = the same ID used in POST /{this_id}/messages to send replies"time": 1747231892,
"messaging": [{
"sender": { "id": "12345678901234" }, // IGSID of the customer"recipient": { "id": "987654321098765" }, // same as entry.id"message": { "text": "Hello!" }
}]
}]
}
// entry[0].id tells you: which of your client's Instagram accounts received this DM// Use it to look up: Page Access Token, client_id, database shard, handler
قد يحتوي entry على عدة عناصر في طلب POST واحد. إذا استقبل حسابان من حسابات عملائك رسائل DM ضمن نفس دفعة التسليم، فقد تجمعها Meta في طلب POST واحد بعنصرين في مصفوفة entry. كرّر دائماً على جميع العناصر، وليس فقط entry[0]. لكل عنصر حقل id خاص به يشير إلى حساب Instagram مختلف.
المعمارية: عنوان واحد، عملاء كثيرون
معمارية توجيه webhook متعدد الحسابات
IG_ID: 987654321
@ClientA Brand
IG_ID: 111222333
@ClientB Store
IG_ID: 444555666
@ClientC Fashion
IG_ID: 777888999
@ClientD Beauty
→→→
عنوان webhook واحد
yourserver.com/webhook
→→→
التوجيه بحسب entry[0].id
معالج Client A
Token A · DB shard A
معالج Client B
Token B · DB shard B
معالج Client C
Token C · DB shard C
معالج Client D
Token D · DB shard D
إدارة الرموز: رمز واحد لكل حساب
كل حساب Instagram Business يتطلب رمز Page Access Token خاصاً به — يُولَّد لصفحة Facebook المرتبطة بذلك الحساب. هذا الرمز هو ما تستخدمه لاستدعاء Instagram Send API لذلك الحساب تحديداً. لا يمكنك استخدام رمز Client A لإرسال الرسائل من حساب Client B.
إرشادات تخزين الرموز:
التشفير في حالة السكون. رموز Page Access Token اعتمادات حساسة. خزّنها مشفّرة في قاعدة بياناتك (AES-256) وليس كنص صريح. فكّ التشفير فقط عند الحاجة لاستدعاء API.
لا تُسجّل الرموز أبداً. تأكد من أن معالج webhook ومُسجّل الأخطاء وخط تحليلاتك لا يسجّلون قيمة الرمز الخام أبداً.
تدوير الرموز. رموز Page Access Token طويلة الأمد لا تنتهي صلاحيتها افتراضياً، لكن دوّرها دورياً (فصلياً) وكلما غادر عميل المنصة. أبطل الرموز فوراً للحسابات التي تنفصل عن منصتك.
رمز واحد لكل حساب، يُحمَّل وقت الطلب. لا تُخزّن الرموز مؤقتاً في الذاكرة إلى ما لا نهاية — حمّلها من قاعدة البيانات وقت الطلب حتى يصبح الإبطال نافذاً فوراً.
ثلاثة جداول تغطي نموذج بيانات تعدد الحسابات كاملاً. جدول ig_accounts هو جدول التوجيه — يُستعلَم عنه مع كل حدث وارد. جدول igsids يخزّن معرّفات المستخدمين ضمن نطاق كل حساب، مما يضمن العزل التام بين العملاء.
جدول ig_accounts — سجل التوجيه
العمود
النوع
ملاحظات
ig_business_id PK
VARCHAR(64)
entry[0].id من webhook — وأيضاً معامل المسار في Send API. خزّنه كسلسلة نصية.
client_id FK
UUID
يرجع إلى جدول العملاء. كل استعلام يصفّي به.
encrypted_token
TEXT
Page Access Token مشفّر بـ AES-256. فكّ التشفير وقت الطلب فقط.
ig_username
VARCHAR(64)
تسمية مقروءة للبشر. @handle لحساب Instagram.
active
BOOLEAN
اضبطه على false عند مغادرة العميل. يمنع معالجة الأحداث من حسابات منفصلة.
created_at
TIMESTAMPTZ
وقت ربط الحساب.
token_rotated_at
TIMESTAMPTZ
تتبّع عمر الرمز لفرض سياسة التدوير.
جدول igsids — معرّفات المستخدمين لكل حساب
العمود
النوع
ملاحظات
igsid PK part
VARCHAR(64)
Instagram-Scoped ID للمستخدم. ضمن نطاق هذا الحساب فقط.
ig_business_id PK partFK
VARCHAR(64)
مفتاح أساسي مركّب مع igsid. نفس المستخدم يراسل عميلين = IGSIDs مختلفان.
client_id FK
UUID
مُكرّر لتسريع الاستعلامات ضمن نطاق العميل.
first_seen
TIMESTAMPTZ
أول DM من هذا الـ IGSID إلى هذا الحساب.
last_message
TIMESTAMPTZ
لتتبّع نافذة الـ 24 ساعة لكل حساب.
metadata
JSONB
أي بيانات خاصة بالعميل (بريد إلكتروني، اسم، معرّف عميل Shopify، إلخ.)
معالج التوجيه متعدد الحسابات
هذا هو معالج طلب POST في webhook الذي يعالج الأحداث بشكل صحيح من أي عدد من حسابات Instagram المتصلة. كل اهتمامات الإنتاج مغطّاة: عناصر متعددة في طلب POST واحد، ومعرّفات حسابات غير معروفة، وحسابات غير نشطة، وعزل سياق نظيف لكل عميل.
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); // always acknowledge immediatelyconst body = req.body;
if (body.object !== 'instagram') return;
// Process ALL entries — Meta may batch multiple accounts in one POSTfor (const entry of body.entry) {
const igBusinessId = entry.id; // ← THE ROUTING KEY// Load account config — resolves token, client_id, and active statusconst account = await db.query(
'SELECT * FROM ig_accounts WHERE ig_business_id = $1 AND active = true',
[igBusinessId]
);
if (!account.rows[0]) {
// Unknown or disconnected account — log and skip
console.warn(`Unknown/inactive IG account: ${igBusinessId}`);
continue;
}
const { client_id, encrypted_token, ig_username } = account.rows[0];
const pageToken = decryptToken(encrypted_token);
// Build client-scoped context — injected into all handlersconst ctx = {
igBusinessId, // used for Send API path
clientId: client_id,
pageToken, // for sending replies
accountName: ig_username,
sendDM: (igsid, text) => sendInstagramDM(igBusinessId, pageToken, igsid, text),
logEvent: (data) => analytics.log({ ...data, client_id, igBusinessId }),
};
// Process each messaging event within this account's entryfor (const event of (entry.messaging || [])) {
// Process in try/catch so one account's errors don't affect otherstry {
awaitprocessEvent(event, ctx);
} catch (err) {
// Log with client context — never re-throw (would skip remaining accounts)
console.error(`Error processing event for ${ig_username} [${igBusinessId}]:`, err.message);
await errorTracker.capture(err, { igBusinessId, client_id });
}
}
}
});
// The client-context-aware Send API wrapperasync 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();
}
عزل بيانات العملاء: منع التلوّث المتبادل
في النظام متعدد المستأجرين، أسوأ نمط فشل هو أن تظهر بيانات عميل في سياق عميل آخر. كل استعلام في قاعدة البيانات يمسّ بيانات المستخدمين — تاريخ المحادثات، وسجلات IGSID، والتحليلات — يجب أن يُصفَّى بـ client_id وig_business_id معاً.
Node.js — client-scoped database operations
clientDB.js
// ✗ Wrong — no client scope, could return any account's conversationsconst convos = await db.query('SELECT * FROM conversations WHERE igsid = $1', [igsid]);
// ✓ Correct — always scope by ig_business_id AND 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: pass ctx everywhere, never pass raw IDs ────────────────────// Upsert IGSID record scoped to this accountasync 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]
);
}
// Log a conversation message scoped to this accountasync 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]
);
}
معالجة الأخطاء وقواطع الدائرة
في نظام webhook ذي حساب واحد، يمكن لخطأ أن يتصاعد ويوقف كل المعالجة. في النظام متعدد الحسابات، لا يجب أبداً أن يمنع خطأ في Client A معالجة الأحداث للعملاء B وC وD. هناك نمطان أساسيان:
try/catch لكل حساب (وقد عُرض أعلاه): غلّف معالجة أحداث كل حساب داخل try/catch خاص به داخل حلقة entry. سجّل الأخطاء مع سياق العميل. لا تُعِد رمي الاستثناء أبداً — استثناء معاد رميه داخل الحلقة سيُجهض كل العناصر المتبقية.
قاطع دائرة لكل حساب: إذا كان حساب ما يُنتج أخطاء متكررة (رمز غير صالح، تجاوز حد المعدل، إبطال صلاحية التطبيق)، أوقف معالجة أحداث ذلك الحساب مؤقتاً بدلاً من الطَرق على نقطة نهاية معطّلة مع كل حدث.
Node.js — per-account circuit breaker
circuitBreaker.js
const errorCounts = newMap(); // igBusinessId → { count, firstErrorAt }constCIRCUIT_THRESHOLD = 5; // open circuit after 5 errorsconstRESET_AFTER_MS = 10 * 60 * 1000; // reset after 10 minutesfunctionisCircuitOpen(igBusinessId) {
const state = errorCounts.get(igBusinessId);
if (!state) returnfalse;
// Auto-reset after 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`);
// Alert your ops team via Slack/PagerDuty here
}
}
// In the router's per-account loop: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);
}
إعداد حساب عميل جديد
كلما ربط عميل جديد حسابه على Instagram بمنصتك، تحتاج إلى تنفيذ تسلسل محدد من الخطوات. بناء هذا كتدفّق إعداد رسمي (بدلاً من إدخالات قاعدة بيانات يدوية) يجعل التوسّع إلى عملاء كثيرين ممكناً:
OAuth أو جمع الرموز يدوياً: إما أن تنفّذ Facebook OAuth (للخدمة الذاتية) أو تُولّد Page Access Token يدوياً في بوابة مطوري Meta. يجب أن يتضمن الرمز صلاحيات instagram_manage_messages وpages_messaging وpages_read_engagement.
استخرج معرّف حساب Instagram Business: استدعِ GET /me?fields=instagram_business_account مع Page Access Token. القيمة instagram_business_account.id في الاستجابة هي القيمة التي تخزّنها كـ ig_business_id في جدول حساباتك.
تحقق من أن الرمز يملك صلاحية الرسائل: اختبر باستدعاء GET /{ig_business_id}?fields=id,name. إذا نجح، فإن الرمز يملك الصلاحيات المطلوبة.
أدخِله في جدول حساباتك: خزّن الرمز المشفّر وclient_id وig_business_id. اضبط active = true.
webhook الخاص بك يستقبل أحداثهم بالفعل. لا حاجة لإعادة التسجيل. اشتراك webhook القائم على تطبيق Facebook الخاص بك يبدأ تلقائياً بتسليم الأحداث لأي حساب يمنح تطبيقك الصلاحيات. ويتولّى البحث في قاعدة البيانات من جانب المُوجِّه التعامل مع الحساب الجديد تلقائياً.
Node.js — client onboarding sequence
onboarding.js
async functiononboardInstagramAccount(clientId, pageAccessToken) {
// Step 1: Resolve Instagram Business Account ID from the 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;
// Step 2: Get the account username for human-readable labelingconst igRes = awaitfetch(
`https://graph.facebook.com/v21.0/${igBusinessId}?fields=username&access_token=${pageAccessToken}`
);
const igAccount = await igRes.json();
// Step 3: Encrypt and store the 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]
);
// Step 4: No webhook re-registration needed — your existing webhook subscription// on your Facebook App automatically covers this account's events.// The router's DB lookup (ig_accounts WHERE ig_business_id = $1) handles it.
console.log(`✓ Onboarded @${igAccount.username} [${igBusinessId}] for client ${clientId}`);
return { igBusinessId, username: igAccount.username };
}
SocialHook: الطبقة المُدارة لتعدد الحسابات
بناء وصيانة مُوجِّه webhook متعدد الحسابات — مع التشفير وقواطع الدائرة وعزل العملاء وتدفقات الإعداد — يستغرق أسابيع من العمل على البنية التحتية التي ليست منتجك الأساسي. SocialHook يتولّى كل هذا كبنية تحتية مُدارة.
اربط جميع حسابات عملائك على Instagram بمساحة عمل SocialHook واحدة. تصل الأحداث من كل حساب عميل إلى نقطة نهاية webhook الوحيدة لديك بالصيغة المُوحَّدة، وسياق العميل محلول بالفعل:
SocialHook — multi-account events already routed
// Every event from any connected account:
{
"platform": "instagram",
"event": "message.received",
"ig_business_id": "987654321098765", // ← already extracted"account_name": "clientA_brand", // ← human-readable label"from": "12345678901234", // ← IGSID"message": { "type": "text", "body": "Hello!" },
"signature_verified": true
}
// Your handler uses ig_business_id for routing — no DB lookup needed
app.post('/webhook', express.json(), async (req, res) => {
res.sendStatus(200);
const { ig_business_id, account_name, from, message } = req.body;
// Route to client-specific handler by ig_business_idawait handlers[ig_business_id]?.handle(from, message);
// Or simply: use ig_business_id as the client partition key in your own DB
});
FAQ
أسئلة شائعة
هل يمكن لعنوان webhook واحد على Instagram استقبال الأحداث من عدة حسابات؟
نعم. معمارية webhook من Meta متعددة المستأجرين بحكم التصميم. تسجيل تطبيق Facebook واحد يُسلّم الأحداث من كل حسابات Instagram التي منحت تطبيقك صلاحية instagram_manage_messages. تصل الأحداث من حسابات مختلفة في جسم طلب POST نفسه، ويُميَّز بينها عبر entry[0].id — وهو معرّف حساب Instagram Business. يقرأ مُوجِّهك هذا الحقل ويُرسل الحدث إلى معالج العميل الصحيح.
كيف أعرف أي حساب Instagram أرسل حدث webhook؟
تحقق من entry[0].id في حمولة webhook. هذا هو معرّف حساب Instagram Business (IGID) الذي استقبل الـ DM. وهو أيضاً المعرّف الرقمي الذي تستخدمه كمعامل مسار عند استدعاء Send API: POST /{entry[0].id}/messages. خزّن خريطة تربط هذا المعرّف بـ Page Access Token الخاص بعميلك وclient_id وسياق قاعدة بياناته. ابحث عنه مع كل حدث وارد.
هل أحتاج إلى إعادة تسجيل webhook عند إضافة حساب Instagram جديد؟
لا. تسجيل webhook على تطبيق Facebook الخاص بك ثابت — لا يتغيّر لكل حساب. عندما يمنح حساب Instagram جديد تطبيقك صلاحية instagram_manage_messages، تبدأ أحداثهم بالوصول تلقائياً إلى عنوان webhook المسجّل لديك. كل ما عليك فعله هو إدخال اعتماداتهم في جدول توجيه الحسابات. يتولّى مُوجِّهك القائم الباقي عبر البحث في entry[0].id.
هل يمكن أن يحتوي entry على عدة حسابات في طلب POST واحد؟
نعم. إذا استقبلت عدة حسابات متصلة رسائل ضمن نفس دفعة التسليم، فقد تجمعها Meta في طلب POST واحد بعناصر متعددة في مصفوفة entry. كرّر دائماً على جميع العناصر — for (const entry of body.entry) — وليس فقط entry[0]. لكل عنصر id خاص به يشير إلى حساب Instagram Business مختلف.
كيف أمنع أخطاء عميل من التأثير على عملاء آخرين؟
غلّف معالجة أحداث كل حساب داخل try/catch خاص به داخل حلقة entry. سجّل الخطأ مع سياق العميل. لا تُعِد رمي الاستثناء أبداً — استثناء معاد رميه داخل الحلقة يُجهض كل العناصر المتبقية. للأخطاء المتكررة على حساب واحد، نفّذ قاطع دائرة لكل حساب يتخطى ذلك الحساب مؤقتاً. كلا النمطين معروضان في قسم معالجة الأخطاء أعلاه.
ما هو مخطط قاعدة البيانات الصحيح لبيانات Instagram webhook متعدد الحسابات؟
ثلاثة جداول رئيسية: clients (صف لكل عميل تجاري)، وig_accounts (صف لكل حساب Instagram، مع Page Access Token مشفّر وig_business_id كمفتاح أساسي)، وigsids (مفتاح أساسي مركّب من igsid + ig_business_id — هذا يضمن عزل نطاق أسماء IGSID بين الحسابات). كل استعلام على بيانات المستخدمين يجب أن يصفّي بـ ig_business_id وclient_id معاً.
اربط حساب Instagram لكل عميل بـ SocialHook. تصل الأحداث إلى نقطة نهايتك موجَّهة مسبقاً حسب الحساب، ومُتحقَّق منها بـ HMAC، ومُوحَّدة الصيغة. أضف حساب عميل جديد في دقائق — دون تغييرات في الكود، ودون إعادة تسجيل، ودون عناوين webhook جديدة. 50 دولاراً/شهر بصرف النظر عن عدد الحسابات التي تديرها.