> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Instagram onboarding playbook

> Step-by-step playbook for connecting an Instagram Business or Creator account to Orbit — Meta OAuth connect, inbound test, messaging windows and tags, media and quick replies, and the errors you will actually see.

# Instagram Onboarding Playbook

This guide walks you from an empty Instagram channel to a working setup — connect, verify, send your first reply, and handle the window rules and errors that trip up first-timers. It complements the [Instagram channel page](/channels/instagram), which is the API reference; this guide is the ordered path you follow the first time, and re-run whenever you attach an additional Instagram account.

There is no Meta app review to wait on and no registration cycle. The only hard gates are that the Instagram account is a Business or Creator account and that it is linked to a Facebook Page you administer — Meta enforces both, not Orbit.

## 1. Prerequisites

Before you open the dashboard, confirm:

* **An Instagram Business or Creator account.** Personal profiles cannot be connected — Meta rejects their messages. Convert in the Instagram app under **Settings → Account type** if the profile is still personal.
* **A Facebook Page linked to that Instagram account**, with your Facebook user as a Page admin. The link lives in the Instagram app under **Settings → Linked accounts → Facebook**. Meta's OAuth dialog only lists accounts reachable through Pages you control; an unlinked Instagram account never appears.
* **A Meta Business Manager account** at business.facebook.com, if your org centralises Meta assets there. Not strictly required for a single brand, but most portfolio setups run through it.
* **A dashboard role that can connect channels.** The user running Connect must hold a role with channel-connect permission (Owner or Admin by default) — an agent or billing-only role will not see the Connect button.

You do not need a Meta developer app or any contracted Meta seats. Instagram messaging is billed per-conversation on Meta's tiers — see [Pricing](#7-pricing) below.

## 2. Connect Instagram in Orbit

Do this once per Instagram account. One Orbit account can carry several connected accounts (regional brands, sub-brands); repeat these steps for each, and each shows up as its own channel entry.

1. In the dashboard, go to **Channels → Instagram**.
2. Click **Connect Instagram**. Meta's OAuth dialog opens and requests four scopes: `instagram_basic`, `instagram_manage_messages`, `pages_messaging`, and `pages_show_list`.
   * `instagram_basic` identifies the professional account itself; `instagram_manage_messages` is the send/receive grant; the two `pages_*` scopes exist because Instagram messaging is delivered through Page-level plumbing.
3. **Select the Instagram Business / Creator account** to attach. If several Pages are linked, pick the account tied to the right Page.
4. Orbit **subscribes the webhook automatically** — the inbound callback is registered for you and points at Orbit's inbound endpoint. You do not wire anything by hand in Meta's app dashboard.
5. The new channel entry appears under **Channels → Instagram** with the connected account's handle. If more than one account is attached, sends address whichever account owns the conversation — the connected sender is resolved from your API key, not a `from` field.

OAuth is dashboard-only; there is no API endpoint for the Meta sign-in itself.

## 3. Verify the connection and the inbound webhook

Confirm inbound delivery before you build on the channel — a connect that never pushes an inbound event usually means the wrong account got linked.

1. From a personal Instagram account, **send a DM** to the connected business account. The inbound event fires as `message.received` on your registered webhook endpoint and the message lands in the [Inbox](/guides/inbox-setup) with the sender's Instagram-scoped ID attached to the contact.
2. Send a **story mention** (tag the account in a story) and a **story reply** from the same personal account. Both arrive as `message.received` as well — a mention carries `metadata.story_mention.url`; a reply carries `metadata.story_reply` (`url` and/or `id`).
3. If you filter on quick replies later, tap a quick-reply button in a thread where one was presented — the tap arrives as `message.received` with `quick_reply.payload` populated.

A typical inbound DM webhook payload looks like:

```json theme={null}
{
  "event": "message.received",
  "channel": "instagram",
  "message": {
    "text": "Do you ship internationally?",
    "from": "<sender_igsid>"
  },
  "timestamp": "2026-08-27T09:14:03Z"
}
```

Two verification traps:

* **The sender's IGSID is what you send to.** Capture `message.from` (the Instagram-scoped ID) from this inbound event onto the contact — that value is the `to` of every outbound send.
* **Signature scheme.** Instagram webhooks are signed with Meta's `X-Hub-Signature-256` header (the same HMAC scheme as WhatsApp), not the Standard Webhooks scheme Orbit uses for its own outbound webhooks. Verify per the channel page's signature section before trusting the body.

## 4. Send your first outbound message

Inside the 24-hour window, a reply is a single call:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages/instagram \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "<recipient_igsid>",
    "messaging_type": "RESPONSE",
    "message": { "text": "Thanks for reaching out! We are checking your order now." }
  }'
```

A `queued` response means Orbit accepted the send; delivery itself is reported by the webhook lifecycle on the [channel page](/channels/instagram).

### Window rules

The `messaging_type` field is not decoration — Meta enforces it, and misuse is the single most common Instagram send failure:

| `messaging_type` | Use it when                                            | Constraint                                                                                                                                                               |
| ---------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `RESPONSE`       | Replying within 24h of the user's last inbound.        | Any content; this is conversational default.                                                                                                                             |
| `UPDATE`         | A proactive, non-promotional update inside the window. | Still gated by the 24h window.                                                                                                                                           |
| `MESSAGE_TAG`    | Sending outside the 24h window.                        | Requires a top-level `tag` from Meta's approved list (`HUMAN_AGENT`, `ACCOUNT_UPDATE`, `POST_PURCHASE_UPDATE`, `CONFIRMED_EVENT_UPDATE`) and only transactional content. |

Rule of thumb: work inside `RESPONSE` whenever you can. When a reply slips past 24 hours, wait for re-engagement (re-opens the window) or switch to `MESSAGE_TAG` with a genuinely transactional tag — do not retry the same call and expect the window to ignore you. `HUMAN_AGENT` extends the reply window to seven days for human support, and is worth naming when you design an inbox SLA.

## 5. Handle media and quick-reply postbacks

Outbound media goes through the `attachment` field instead of `text`:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages/instagram \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "<recipient_igsid>",
    "messaging_type": "RESPONSE",
    "message": {
      "attachment": {
        "type": "image",
        "payload": { "url": "https://cdn.example.com/size-guide.png" }
      }
    }
  }'
```

`attachment.type` is one of `image`, `audio`, `video`, `file`; the URL must be publicly fetchable by Meta. For a structured carousel, Orbit supports the **generic template** via two `metadata` keys — `template_type: generic` and a JSON-encoded `elements` string. The exact shape (and the fallback to plain `text` when `elements` is malformed) is on the [channel page](/channels/instagram#generic-templates).

Inbound, media and story interactions arrive as the same `message.received` event family, and quick-reply taps surface as postbacks with `quick_reply.payload` set — branch on that payload in your webhook consumer or in an inbox automation flow. Attaching quick-reply buttons to outbound DMs is not currently supported by the send API; quick replies you react to originate from flows you run behind Meta surfaces you already expose.

## 6. Common errors and fixes

The send API returns one of three `error.code` values; `MESSAGE_SEND_FAILED` is the umbrella under which Meta-side rejections hide, with upstream detail in `details.provider_status`.

* **`INVALID_RECIPIENT` (422)** — the `to` field was missing or empty, or treated a username/phone number as a recipient. `to` must be the Instagram-scoped ID captured from an inbound `message.received`; store it on the contact at the moment it arrives. A first-send `INVALID_RECIPIENT` almost always means the capture step was skipped and a placeholder was sent instead.
* **Unlinked Page / non-professional account (`MESSAGE_SEND_FAILED`)** — the Instagram account was never linked to the Facebook Page, so Meta cannot route its messages; or the account is still a personal profile. Link the account under Instagram **Settings → Linked accounts**, convert it to Business or Creator, then reconnect from **Channels → Instagram → Reconnect**.
* **Insufficient scopes (`MESSAGE_SEND_FAILED`)** — one of the four OAuth scopes was declined during Connect, so Orbit cannot send or receive on the account's behalf. Reconnect and accept `instagram_basic`, `instagram_manage_messages`, `pages_messaging`, and `pages_show_list` in full.
* **Expired page token (`MESSAGE_SEND_FAILED` with a 401-class `provider_status`)** — Meta rotated the token. Reconnect from **Channels → Instagram → Reconnect**; no other side of the integration needs to change.
* **Window closed (`MESSAGE_SEND_FAILED`)** — a `RESPONSE`/`UPDATE` more than 24h after the last inbound, or a `MESSAGE_TAG` without an approved `tag`. Send with a valid tag for transactional traffic, or wait for re-engagement.
* **`RATE_LIMIT_EXCEEDED` (429)** — the send would exceed the per-tenant hourly Instagram quota (200/hour by default, matching Meta's current cap per account). Back off and retry later; for planned batches, use the campaigns API, which paces sends, rather than a tight single-message loop.

## 7. Pricing

Instagram messaging follows Meta's per-conversation pricing tiers — conversations are metered by Meta, and Orbit bills pass-through on the same model. International destinations vary; the dashboard shows the exact rate per destination before send. Public starter rates are on the [pricing page](https://orbit.devotel.io/pricing).

## Where to go next

* [Instagram channel page](/channels/instagram) — full API reference: generic templates, signature verification, rate limits, error tables.
* [Send and receive messages](/guides/send-receive-messages) — the cross-channel send pattern once you run more than one channel.
* [Inbox setup](/guides/inbox-setup) — route inbound Instagram traffic to your team.
* [Messenger onboarding](/guides/messenger-onboarding) — the parallel playbook if you also attach the brand's Facebook Page.
* [Best practices](/guides/best-practices) — platform-wide sending hygiene.
