Inbound Instagram DMs become draft/confirmed orders, and the product catalog is pushed to Instagram Shopping. Audience: new dev, no Eziseller context.
1. Overview
Eziseller integrates with the Meta Graph API (Instagram Business) for two distinct jobs: (1) DM-driven ordering — a customer DMs a seller, the message is parsed by the AI parser and turned into a DraftOrder or Order, and (2) Shopping catalog sync — Eziseller products are mirrored to an Instagram Catalog so they can be tagged in posts/reels and surfaced via "View Shop". The webhook endpoint (/webhooks/meta) is shared with WhatsApp; routing happens on body.object === 'instagram' | 'page'. Multi-tenant: each seller stores their own encrypted IG access token, page ID, and business account ID.
2. Architecture
The meta-webhooks route verifies the X-Hub-Signature-256 (HMAC), finds the seller by instagram_business_account_id, and hands each messaging event to InstagramDMProcessor. Shopping sync is a separate admin-triggered path that reads Product rows and pushes them to the seller's Instagram Catalog.
3. Data model
InstagramUserCredentials— per-user encrypted access token + verify token,pageId,businessAccountId. Separate from theStore.instagramPageIdfield used for quick lookups.InstagramCatalogs/InstagramCatalogSyncLogs— one catalog per user; sync logs track each full/incremental push.InstagramOrders— link table from IG order payload to the canonicalOrder.Conversation(shared with WhatsApp) distinguishes channel viachannel = 'instagram'.Order.messageSource = 'instagram'marks orders originating from DMs.
Full schema: schema.prisma:L1042-L1210.
4. Key flows
4.1 Inbound DM to draft order
extractTextFromEvent handles five event shapes: plain text, quick-reply payloads, postbacks, product referrals ("I'm interested in product {id}"), and returns null for image-only DMs (no OCR).
4.2 Shopping catalog sync
5. Lifecycle
InstagramCatalogs.status transitions above; autoSync + syncInterval drive periodic re-sync.
6. Key files
- instagram-business-api.ts:L66-L200 — Graph v18 client, config loader, rate limits (200 calls/hour)
- instagram-dm-processor.ts:L85-L218 — DM → order pipeline, event-type extraction
- instagram-shopping-service.ts:L57-L203 — catalog sync orchestration
- instagram-webhook-service.ts — IG-specific webhook helpers
- meta-webhooks.ts:L117-L365 — shared verify/receive, IG branch at
body.object === 'instagram' - meta-webhooks.ts:L411-L441 —
findSellerByInstagramAccountId(checks credentials table thenStore.instagramPageId) - instagram-dm-webhook.ts — legacy standalone webhook (pre-unification)
- instagram-shopping.ts — admin endpoints for sync / catalog management
- schema.prisma:L1042-L1210 — all IG models
7. Env vars & config
| Var | Required | Purpose | What breaks |
|---|---|---|---|
INSTAGRAM_APP_SECRET | yes | HMAC verify of X-Hub-Signature-256 on the shared /webhooks/meta endpoint (IG app secret, separate from WA) | All IG webhooks rejected with 403 |
INSTAGRAM_VERIFY_TOKEN | yes | Fallback verify token for the initial hub.challenge handshake | Webhook subscription cannot be created in Meta dashboard |
INSTAGRAM_ACCESS_TOKEN / INSTAGRAM_PAGE_ID | no | Dev-only fallback used by routes/instagram-test.ts; production reads per-user creds | Test routes fail; prod unaffected |
ENCRYPTION_KEY | yes | Decrypts InstagramUserCredentials.encryptedAccessToken | All Graph calls fail post-decrypt |
META_APP_SECRET | no | Some older paths reference a shared Meta secret; current code uses separate WHATSAPP_APP_SECRET / INSTAGRAM_APP_SECRET | n/a if IG-specific secret set |
Per-user creds live in InstagramUserCredentials + Store.instagramPageId / Store.instagramUsername.
8. Gotchas & troubleshooting
- IG Business requires a connected Facebook Page. The Graph API exposes IG Business Accounts only through a Page. If a seller connects IG without also granting the Page,
findSellerByInstagramAccountIdreturns null and webhooks silently drop. Fix: Meta Business Suite > connect FB Page to IG Business, then re-link in Eziseller settings. body.objectisinstagramORpage. Meta inconsistently routes some IG DM events through Page webhooks. The dispatcher handles both (meta-webhooks.ts:L117-L123) — don't filter to'instagram'only.- Shopping catalog requires Meta approval. A newly created IG Business cannot push products until Commerce Manager approves the catalog (manual review, can take days). Sync calls return HTTP 200 but products don't appear in-app until approval. Check status in Commerce Manager, not in
InstagramCatalogs.status. - Permission scopes are brittle. Required:
instagram_basic,instagram_manage_messages,pages_messaging,pages_show_list,catalog_management,business_management. Missinginstagram_manage_messagesfails silently onsendMessagewith a 200 OK and no delivery. - Image-only DMs are dropped.
extractTextFromEventreturns null for attachments with no text — there is no OCR. Sellers should be told customers must type product names. - Story mention / reply attribution. When a customer replies to a seller's story,
reply_to.story.idis present butmessage.textmay be empty (sticker-only reactions). Those become skipped events. - Customer identity differs from WhatsApp. IG sender ID is a scoped Page-Scoped ID (PSID), not a phone number.
InstagramCustomers.instagramIdstores the PSID; phone/email/address are regex-scraped from subsequent DMs (instagram-dm-processor.ts:L298-L317) — so the first order is often low-confidence and lands as aDraftOrder. - Rate limit is 200/hour per user (tracked in
InstagramRateLimits). Bulk catalog syncs of >200 products will hit the cap mid-sync and producestatus='partial'. Re-run picks up where it left off becausesyncSingleProductis idempotent onretailer_id. - Two webhook routes exist. The legacy
/api/instagram/dm/webhookstill works but new setups must use the unified/webhooks/meta— otherwise signature verification uses the wrong secret table.
9. Extension points
- Story reply auto-responder.
extractTextFromEventalready detectsreply_to.story.id; route those events to a dedicated handler before parser invocation to answer with a product link. - Comment-to-DM automation.
processInstagramComment(meta-webhooks.ts:L814) is a stub — hook it up to auto-DM commenters a catalog link. - Incremental catalog sync.
InstagramCatalogSyncLogs.syncTypeacceptsincremental; implement it by diffingProduct.updatedAt > lastSyncAtinstead of the current full push. - Product-referral pre-fill. When
referral.product.idarrives, look up the product byretailer_idand skip the parser — go straight to draft-order creation.
10. Related docs
- whatsapp.md — sibling channel, shares webhook endpoint
- messaging-overview.md — cross-channel dispatcher &
Conversationmodel - ai-parser.md —
MessageParsingServicedetails - ../webhooks/webhooks-overview.md — HMAC verification pattern
- ../../01-core/orders/draft-orders.md — where low-confidence IG orders land
- ../../01-core/orders/order-creation-instagram.md — order-creation variant