Integrations
Messaging
Instagram

Instagram

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 the Store.instagramPageId field 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 canonical Order.
  • Conversation (shared with WhatsApp) distinguishes channel via channel = '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

7. Env vars & config

VarRequiredPurposeWhat breaks
INSTAGRAM_APP_SECRETyesHMAC 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_TOKENyesFallback verify token for the initial hub.challenge handshakeWebhook subscription cannot be created in Meta dashboard
INSTAGRAM_ACCESS_TOKEN / INSTAGRAM_PAGE_IDnoDev-only fallback used by routes/instagram-test.ts; production reads per-user credsTest routes fail; prod unaffected
ENCRYPTION_KEYyesDecrypts InstagramUserCredentials.encryptedAccessTokenAll Graph calls fail post-decrypt
META_APP_SECRETnoSome older paths reference a shared Meta secret; current code uses separate WHATSAPP_APP_SECRET / INSTAGRAM_APP_SECRETn/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, findSellerByInstagramAccountId returns null and webhooks silently drop. Fix: Meta Business Suite > connect FB Page to IG Business, then re-link in Eziseller settings.
  • body.object is instagram OR page. 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. Missing instagram_manage_messages fails silently on sendMessage with a 200 OK and no delivery.
  • Image-only DMs are dropped. extractTextFromEvent returns 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.id is present but message.text may 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.instagramId stores 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 a DraftOrder.
  • Rate limit is 200/hour per user (tracked in InstagramRateLimits). Bulk catalog syncs of >200 products will hit the cap mid-sync and produce status='partial'. Re-run picks up where it left off because syncSingleProduct is idempotent on retailer_id.
  • Two webhook routes exist. The legacy /api/instagram/dm/webhook still 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. extractTextFromEvent already detects reply_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.syncType accepts incremental; implement it by diffing Product.updatedAt > lastSyncAt instead of the current full push.
  • Product-referral pre-fill. When referral.product.id arrives, look up the product by retailer_id and skip the parser — go straight to draft-order creation.

10. Related docs