From e7af720be01e92d6f1c361f5c67b9fe74b6445e6 Mon Sep 17 00:00:00 2001
From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com>
Date: Sun, 19 Apr 2026 14:14:22 +0000
Subject: [PATCH] Add Instagram integration API docs for 5 new edge functions
Generated-By: mintlify-agent
---
api-reference/instagram-data-deletion.mdx | 168 ++++++++++++++++++++++
api-reference/instagram-deauth.mdx | 94 ++++++++++++
api-reference/instagram-oauth.mdx | 146 +++++++++++++++++++
api-reference/instagram-post-reply.mdx | 148 +++++++++++++++++++
api-reference/instagram-webhook.mdx | 118 +++++++++++++++
docs.json | 10 ++
6 files changed, 684 insertions(+)
create mode 100644 api-reference/instagram-data-deletion.mdx
create mode 100644 api-reference/instagram-deauth.mdx
create mode 100644 api-reference/instagram-oauth.mdx
create mode 100644 api-reference/instagram-post-reply.mdx
create mode 100644 api-reference/instagram-webhook.mdx
diff --git a/api-reference/instagram-data-deletion.mdx b/api-reference/instagram-data-deletion.mdx
new file mode 100644
index 0000000..32124d8
--- /dev/null
+++ b/api-reference/instagram-data-deletion.mdx
@@ -0,0 +1,168 @@
+---
+title: "Instagram data deletion"
+sidebarTitle: "POST /instagram-data-deletion"
+description: "Handle Instagram data deletion requests from Meta or self-service user requests."
+---
+
+The data deletion endpoint supports two modes: Meta-initiated deletion (via a signed request when a user requests deletion through Instagram) and self-service deletion (when a logged-in NAWA user requests deletion of their Instagram data from the NAWA dashboard).
+
+
+ This endpoint is required by Meta for live mode approval. The Meta callback URL must be registered in your Meta app dashboard under "Data Deletion Request URL". The self-service mode is used by the NAWA frontend at `trynawa.com/data-deletion`.
+
+
+## Mode A: Meta signed request
+
+Meta sends this request when a user initiates data deletion through Instagram settings or Meta's privacy tools.
+
+### Request
+
+`POST` from Meta's servers with `application/x-www-form-urlencoded` or `application/json`:
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `signed_request` | string | Yes | A Meta signed request string. Format: `.`. Verified via HMAC-SHA256 with your Instagram App Secret. |
+
+### Example (form-encoded)
+
+```bash
+curl -X POST https://.supabase.co/functions/v1/instagram-data-deletion \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "signed_request=."
+```
+
+### Response (200)
+
+Meta requires the response to include a `url` and `confirmation_code`:
+
+```json
+{
+ "url": "https://www.trynawa.com/data-deletion/status?code=a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
+ "confirmation_code": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
+}
+```
+
+## Mode B: self-service deletion
+
+A logged-in NAWA user can request deletion of their Instagram data from the NAWA dashboard.
+
+### Request
+
+`POST` with a valid Supabase JWT in the `Authorization` header:
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `self_service` | boolean | Yes | Must be `true` to trigger self-service mode. |
+
+### Example request
+
+
+
+```bash cURL
+curl -X POST https://.supabase.co/functions/v1/instagram-data-deletion \
+ -H "Authorization: Bearer " \
+ -H "Content-Type: application/json" \
+ -d '{"self_service": true}'
+```
+
+```python Python
+import requests
+
+response = requests.post(
+ "https://.supabase.co/functions/v1/instagram-data-deletion",
+ headers={
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",
+ },
+ json={"self_service": True},
+)
+
+data = response.json()
+print(data)
+```
+
+```typescript TypeScript
+const response = await fetch(
+ 'https://.supabase.co/functions/v1/instagram-data-deletion',
+ {
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ self_service: true }),
+ }
+)
+
+const data = await response.json()
+console.log(data)
+```
+
+
+
+### Response (200)
+
+```json
+{
+ "url": "https://www.trynawa.com/data-deletion/status?code=f8e7d6c5-4b3a-2190-a1b2-c3d4e5f6a7b8",
+ "confirmation_code": "f8e7d6c5-4b3a-2190-a1b2-c3d4e5f6a7b8"
+}
+```
+
+## Response fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `url` | string | A status URL where the user (or Meta) can check that the deletion was completed. Format: `https://www.trynawa.com/data-deletion/status?code=`. |
+| `confirmation_code` | string | A UUID that uniquely identifies this deletion request. |
+
+## Error responses
+
+| Status | Error code | Description |
+|--------|------------|-------------|
+| 400 | `invalid_signed_request` | The Meta signed request failed HMAC verification or is malformed. |
+| 401 | `missing_authorization` | Self-service mode requires an `Authorization: Bearer ` header. |
+| 401 | `invalid_session` | The JWT is expired or invalid. |
+| 405 | `method_not_allowed` | Request used a method other than POST. |
+| 500 | `server_misconfigured` | The `INSTAGRAM_APP_SECRET` environment variable is missing. |
+
+## Deletion scope
+
+
+ Data deletion is scoped to Instagram data only. The user's NAWA account, Stripe subscription, YouTube connections, and other platform data are preserved. For full NAWA account deletion, use the separate account deletion flow.
+
+
+The following data is deleted:
+
+| Data | Action |
+|------|--------|
+| `platform_connections` row (Instagram) | Deleted |
+| `comments` where `platform = 'instagram'` for the user | Deleted |
+| `error_logs` audit entries | Retained (anonymized) with a deletion confirmation record |
+
+## How it works
+
+
+
+ For Meta requests: verify the HMAC-SHA256 signed request. For self-service: validate the Supabase JWT.
+
+
+ Look up the `platform_connections` row by Instagram user ID (Meta mode) or NAWA user ID (self-service mode).
+
+
+ Delete all comments with `platform = 'instagram'` for the user, then delete the `platform_connections` row.
+
+
+ Write a confirmation entry to `error_logs` with type `instagram_data_deleted`, including the confirmation code and timestamps.
+
+
+ Return the confirmation code and status URL. Meta displays this URL to the user so they can verify the deletion was completed.
+
+
+
+## Environment variables
+
+| Variable | Description |
+|----------|-------------|
+| `INSTAGRAM_APP_SECRET` | Your Meta app's Instagram App Secret. Used to verify HMAC signatures. |
+| `SUPABASE_URL` | Your Supabase project URL. |
+| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service role key for server-side database operations. |
diff --git a/api-reference/instagram-deauth.mdx b/api-reference/instagram-deauth.mdx
new file mode 100644
index 0000000..db389a0
--- /dev/null
+++ b/api-reference/instagram-deauth.mdx
@@ -0,0 +1,94 @@
+---
+title: "Instagram deauthorize"
+sidebarTitle: "POST /instagram-deauth"
+description: "Handle Instagram deauthorization callbacks when a user revokes NAWA from their Instagram settings."
+---
+
+The deauthorize callback fires when a user revokes NAWA from their Instagram account settings (instagram.com/accounts/manage_access or the Instagram app). Meta sends an HMAC-signed request that NAWA verifies before marking the connection as inactive.
+
+
+ This endpoint is required by Meta for live mode approval. It runs as a Supabase Edge Function and is called by Meta, not by your application. You register this URL in your Meta app's Instagram Basic Display or Business Login settings under "Deauthorize Callback URL".
+
+
+## Request
+
+### Method
+
+`POST` from Meta's servers to your Supabase Edge Function URL:
+
+```
+https://.supabase.co/functions/v1/instagram-deauth
+```
+
+### Content type
+
+Meta sends the request as `application/x-www-form-urlencoded` with a single parameter:
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `signed_request` | string | Yes | A Meta signed request string in the format `.`. The signature is an HMAC-SHA256 of the payload using your Instagram App Secret. |
+
+### Signed request payload
+
+After verification, the decoded payload contains:
+
+```json
+{
+ "algorithm": "HMAC-SHA256",
+ "issued_at": 1713542969,
+ "user_id": "17841400123456789"
+}
+```
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `algorithm` | string | Always `HMAC-SHA256`. |
+| `issued_at` | number | Unix timestamp of when the request was issued. |
+| `user_id` | string | The Instagram user ID that revoked access. |
+
+## Response
+
+### Success response (200)
+
+```json
+{
+ "success": true
+}
+```
+
+### Error responses
+
+| Status | Error code | Description |
+|--------|------------|-------------|
+| 400 | `invalid_signed_request` | The signed request failed HMAC verification, is malformed, or is missing. |
+| 405 | `method_not_allowed` | Request used a method other than POST. |
+| 500 | `server_misconfigured` | The `INSTAGRAM_APP_SECRET` environment variable is missing. |
+
+## What happens on deauthorization
+
+
+
+ NAWA verifies the HMAC-SHA256 signature using your Instagram App Secret via constant-time comparison.
+
+
+ The Instagram user ID from the payload is matched against `platform_connections` where `platform = 'instagram'` and `platform_user_id` matches.
+
+
+ The connection row is updated: `is_active` is set to `false`, and `access_token` and `refresh_token` are cleared.
+
+
+ An audit entry is written to `error_logs` with type `instagram_deauthorized` for compliance tracking.
+
+
+
+
+ Deauthorization does not delete user data. It only marks the Instagram connection as inactive and clears stored tokens. For data deletion, see [Instagram data deletion](/api-reference/instagram-data-deletion).
+
+
+## Environment variables
+
+| Variable | Description |
+|----------|-------------|
+| `INSTAGRAM_APP_SECRET` | Your Meta app's Instagram App Secret. Used to verify the HMAC-SHA256 signature on the signed request. |
+| `SUPABASE_URL` | Your Supabase project URL. |
+| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service role key for server-side database operations. |
diff --git a/api-reference/instagram-oauth.mdx b/api-reference/instagram-oauth.mdx
new file mode 100644
index 0000000..7341545
--- /dev/null
+++ b/api-reference/instagram-oauth.mdx
@@ -0,0 +1,146 @@
+---
+title: "Instagram OAuth"
+sidebarTitle: "POST /instagram-oauth"
+description: "Exchange an Instagram authorization code for a long-lived access token and connect an Instagram Business or Creator account."
+---
+
+Exchange an Instagram authorization code for a long-lived (60-day) access token and upsert the connection into your NAWA account. Only Instagram Business and Media Creator accounts are supported. Personal accounts are rejected.
+
+
+ This endpoint is part of the NAWA platform integration layer. It runs as a Supabase Edge Function and is called by the NAWA frontend during the Instagram connection flow. You do not need to call this endpoint directly unless you are building a custom integration.
+
+
+## Prerequisites
+
+- An Instagram Business or Media Creator account
+- A valid Instagram authorization code obtained via the [Instagram Business Login](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login) flow
+- A NAWA user account (the `user_id` from your Supabase auth session)
+
+## Request
+
+### Method
+
+`POST` to your Supabase Edge Function URL:
+
+```
+https://.supabase.co/functions/v1/instagram-oauth
+```
+
+### Body parameters
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `code` | string | Yes | The short-lived authorization code from Instagram's OAuth redirect. |
+| `redirect_uri` | string | Yes | The exact redirect URI registered with your Instagram app. Must match the URI used in the authorization request. |
+| `user_id` | string | Yes | The NAWA user ID (Supabase auth UID) to associate this Instagram connection with. |
+
+### Example request
+
+
+
+```bash cURL
+curl -X POST https://.supabase.co/functions/v1/instagram-oauth \
+ -H "Content-Type: application/json" \
+ -d '{
+ "code": "AQB...your_instagram_code",
+ "redirect_uri": "https://trynawa.com/auth/instagram/callback",
+ "user_id": "d5f7a1b2-3c4e-5f6a-7b8c-9d0e1f2a3b4c"
+ }'
+```
+
+```python Python
+import requests
+
+response = requests.post(
+ "https://.supabase.co/functions/v1/instagram-oauth",
+ json={
+ "code": "AQB...your_instagram_code",
+ "redirect_uri": "https://trynawa.com/auth/instagram/callback",
+ "user_id": "d5f7a1b2-3c4e-5f6a-7b8c-9d0e1f2a3b4c",
+ },
+)
+
+data = response.json()
+print(data)
+```
+
+```typescript TypeScript
+const response = await fetch(
+ 'https://.supabase.co/functions/v1/instagram-oauth',
+ {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ code: 'AQB...your_instagram_code',
+ redirect_uri: 'https://trynawa.com/auth/instagram/callback',
+ user_id: 'd5f7a1b2-3c4e-5f6a-7b8c-9d0e1f2a3b4c',
+ }),
+ }
+)
+
+const data = await response.json()
+console.log(data)
+```
+
+
+
+## Response
+
+### Success response (200)
+
+```json
+{
+ "success": true,
+ "connection_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
+ "handle": "your_ig_username",
+ "account_type": "BUSINESS",
+ "expires_at": "2026-06-18T17:09:29.000Z"
+}
+```
+
+### Result fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `success` | boolean | `true` when the connection was created or updated. |
+| `connection_id` | string | The UUID of the `platform_connections` row. |
+| `handle` | string | The Instagram username of the connected account. |
+| `account_type` | string | Instagram account type: `BUSINESS` or `MEDIA_CREATOR`. |
+| `expires_at` | string | ISO 8601 timestamp when the long-lived token expires (approximately 60 days from connection). |
+
+### Error responses
+
+| Status | Error code | Description |
+|--------|------------|-------------|
+| 400 | `invalid_json` | Request body is not valid JSON. |
+| 400 | `missing_params` | One or more required parameters (`code`, `redirect_uri`, `user_id`) are missing. |
+| 400 | `short_token_exchange_failed` | Instagram rejected the authorization code. The code may be expired or already used. |
+| 400 | `long_token_exchange_failed` | The short-lived token could not be exchanged for a long-lived token. |
+| 400 | `user_fetch_failed` | Could not retrieve the Instagram user profile. |
+| 400 | `account_type_not_supported` | The Instagram account is a Personal account. Only Business and Media Creator accounts are supported. |
+| 405 | `method_not_allowed` | Request used a method other than POST. |
+| 500 | `server_misconfigured` | The `INSTAGRAM_APP_ID` or `INSTAGRAM_APP_SECRET` environment variable is missing. |
+| 500 | `db_upsert_failed` | The database upsert for `platform_connections` failed. |
+| 500 | `internal_error` | An unexpected error occurred. |
+
+
+ Instagram Personal accounts are not supported. If a user attempts to connect a Personal account, the endpoint returns a `400` with `account_type_not_supported`. The user must convert their account to a Business or Creator account in Instagram settings before connecting.
+
+
+## How it works
+
+The OAuth exchange follows a three-step flow:
+
+
+
+ NAWA sends the authorization code to `api.instagram.com/oauth/access_token` to obtain a short-lived token (valid for about 1 hour).
+
+
+ The short-lived token is exchanged at `graph.instagram.com/access_token` for a long-lived token valid for 60 days.
+
+
+ NAWA fetches the user's profile (`id`, `username`, `account_type`) from `graph.instagram.com/me`, validates the account type, and upserts the connection into `platform_connections`.
+
+
+
+If the user already has an Instagram connection, the existing row is updated with the new token and metadata (upsert on `user_id` + `platform`).
diff --git a/api-reference/instagram-post-reply.mdx b/api-reference/instagram-post-reply.mdx
new file mode 100644
index 0000000..e40c086
--- /dev/null
+++ b/api-reference/instagram-post-reply.mdx
@@ -0,0 +1,148 @@
+---
+title: "Instagram post reply"
+sidebarTitle: "POST /instagram-post-reply"
+description: "Post a reply to an Instagram comment via the Instagram Graph API."
+---
+
+Post a reply to an Instagram comment through the Instagram Graph API. NAWA validates the comment, retrieves the active Instagram connection, posts the reply, and updates the comment status to `responded`.
+
+
+ This endpoint runs as a Supabase Edge Function. It is called by the NAWA frontend when a user approves or sends a reply to an Instagram comment.
+
+
+## Request
+
+### Method
+
+`POST` to your Supabase Edge Function URL:
+
+```
+https://.supabase.co/functions/v1/instagram-post-reply
+```
+
+### Body parameters
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `comment_id` | string | Yes | The NAWA comment UUID (from the `comments` table). |
+| `reply_text` | string | Yes | The reply text to post. Em dashes and en dashes are automatically replaced with hyphens. |
+| `user_id` | string | Yes | The NAWA user ID. Must match the owner of the comment. |
+
+### Example request
+
+
+
+```bash cURL
+curl -X POST https://.supabase.co/functions/v1/instagram-post-reply \
+ -H "Content-Type: application/json" \
+ -d '{
+ "comment_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
+ "reply_text": "Thanks for watching! More content coming soon.",
+ "user_id": "d5f7a1b2-3c4e-5f6a-7b8c-9d0e1f2a3b4c"
+ }'
+```
+
+```python Python
+import requests
+
+response = requests.post(
+ "https://.supabase.co/functions/v1/instagram-post-reply",
+ json={
+ "comment_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
+ "reply_text": "Thanks for watching! More content coming soon.",
+ "user_id": "d5f7a1b2-3c4e-5f6a-7b8c-9d0e1f2a3b4c",
+ },
+)
+
+data = response.json()
+print(data)
+```
+
+```typescript TypeScript
+const response = await fetch(
+ 'https://.supabase.co/functions/v1/instagram-post-reply',
+ {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ comment_id: 'a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d',
+ reply_text: 'Thanks for watching! More content coming soon.',
+ user_id: 'd5f7a1b2-3c4e-5f6a-7b8c-9d0e1f2a3b4c',
+ }),
+ }
+)
+
+const data = await response.json()
+console.log(data)
+```
+
+
+
+## Response
+
+### Success response (200)
+
+```json
+{
+ "success": true,
+ "platform_reply_id": "17858893269789999"
+}
+```
+
+### Result fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `success` | boolean | `true` when the reply was posted to Instagram. |
+| `platform_reply_id` | string or null | The Instagram Graph API ID of the posted reply. `null` if Instagram did not return an ID (rare). |
+
+### Error responses
+
+| Status | Error code | Description |
+|--------|------------|-------------|
+| 400 | `invalid_json` | Request body is not valid JSON. |
+| 400 | `missing_params` | One or more required parameters (`comment_id`, `reply_text`, `user_id`) are missing. |
+| 400 | `empty_reply` | The `reply_text` is empty after sanitization. |
+| 400 | `wrong_platform` | The comment exists but belongs to a different platform (not Instagram). |
+| 400 | `no_active_connection` | The user does not have an active Instagram connection, or the access token is missing. |
+| 401 | `instagram_reply_failed` | Instagram returned error code `190`, indicating the access token is invalid or expired. The connection is automatically marked inactive. |
+| 404 | `comment_not_found` | No comment found with the given `comment_id` for this `user_id`. |
+| 405 | `method_not_allowed` | Request used a method other than POST. |
+| 429 | `rate_limited` | Instagram's rate limit was hit. Error codes `4`, `17`, `32`, or `613` from the Graph API. Retry after a delay. |
+| 502 | `instagram_reply_failed` | Instagram rejected the reply for a reason other than auth or rate limiting. Check `detail` in the response for the Graph API error message. |
+
+### Error response body
+
+```json
+{
+ "error": "rate_limited",
+ "status": 429,
+ "ig_error_code": 4,
+ "detail": "Application request limit reached"
+}
+```
+
+## Instagram Graph API error handling
+
+The endpoint handles specific Meta Graph API error codes:
+
+| Error code | Meaning | NAWA behavior |
+|------------|---------|---------------|
+| `190` | Token invalid or expired | Marks the connection as inactive (`is_active = false`). Returns `401`. |
+| `4` | Application-level rate limit | Returns `429` with `rate_limited` error. |
+| `17` | User-level rate limit | Returns `429` with `rate_limited` error. |
+| `32` | Page-level rate limit | Returns `429` with `rate_limited` error. |
+| `613` | User-level throttle | Returns `429` with `rate_limited` error. |
+
+
+ When the endpoint returns `401` with `ig_error_code: 190`, the Instagram connection is automatically deactivated. The user must reconnect their Instagram account through the OAuth flow to resume posting replies.
+
+
+## Side effects
+
+On a successful reply:
+- The `comments` row is updated with `status = 'responded'`, `replied = true`, `reply_text`, `reply_timestamp`, and `platform_reply_id`.
+- The `platform_reply_id` column stores the Instagram reply ID for cross-platform reply tracking.
+
+On a failed reply:
+- An `error_logs` entry is created with type `instagram_reply_failed`, including the Graph API error code and subcode.
diff --git a/api-reference/instagram-webhook.mdx b/api-reference/instagram-webhook.mdx
new file mode 100644
index 0000000..a3b53d3
--- /dev/null
+++ b/api-reference/instagram-webhook.mdx
@@ -0,0 +1,118 @@
+---
+title: "Instagram webhook"
+sidebarTitle: "Instagram webhook"
+description: "Receive real-time Instagram comment notifications via Meta's webhook system."
+---
+
+The Instagram webhook endpoint handles two responsibilities: completing Meta's verification handshake (GET) and receiving real-time comment events (POST). When a new comment arrives on a connected Instagram account, NAWA stores it and triggers classification automatically.
+
+
+ This endpoint is registered with Meta as your Instagram webhook callback URL. It runs as a Supabase Edge Function. You do not call it directly - Meta sends events to it.
+
+
+## Webhook verification (GET)
+
+Meta sends a GET request to verify ownership of the webhook URL during app setup.
+
+### Query parameters
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `hub.mode` | string | Yes | Always `subscribe` for verification requests. |
+| `hub.verify_token` | string | Yes | The verify token you configured in the Meta app dashboard. Must match your `INSTAGRAM_WEBHOOK_VERIFY_TOKEN` environment variable. |
+| `hub.challenge` | string | Yes | A challenge string that must be echoed back to confirm ownership. |
+
+### Example verification request
+
+```
+GET /functions/v1/instagram-webhook?hub.mode=subscribe&hub.verify_token=your_token&hub.challenge=abc123
+```
+
+### Verification response
+
+On success, the endpoint returns the `hub.challenge` value as plain text with a `200` status code. On failure, it returns `403 Forbidden`.
+
+## Comment events (POST)
+
+When a comment is posted on any media owned by a connected Instagram account, Meta sends a POST request with the event payload.
+
+### Webhook payload
+
+```json
+{
+ "object": "instagram",
+ "entry": [
+ {
+ "id": "17841400123456789",
+ "time": 1713542969,
+ "changes": [
+ {
+ "field": "comments",
+ "value": {
+ "id": "17858893269789012",
+ "text": "Great content!",
+ "from": {
+ "id": "17841400987654321",
+ "username": "commenter_handle"
+ },
+ "media": {
+ "id": "17846368259789012",
+ "media_product_type": "FEED"
+ },
+ "timestamp": "2026-04-19T17:09:29+0000"
+ }
+ }
+ ]
+ }
+ ]
+}
+```
+
+### Payload fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `object` | string | Always `instagram` for Instagram webhook events. |
+| `entry[].id` | string | The Instagram account ID that received the comment. Used to resolve the NAWA user via `platform_connections`. |
+| `entry[].time` | number | Unix timestamp of the event. |
+| `entry[].changes[].field` | string | The field that changed. NAWA processes `comments` events only. |
+| `entry[].changes[].value.id` | string | The Instagram comment ID. |
+| `entry[].changes[].value.text` | string | The comment text. |
+| `entry[].changes[].value.from.id` | string | The commenter's Instagram user ID. |
+| `entry[].changes[].value.from.username` | string | The commenter's Instagram username. |
+| `entry[].changes[].value.media.id` | string | The media ID that the comment was posted on. |
+| `entry[].changes[].value.timestamp` | string | ISO 8601 timestamp of when the comment was posted. |
+| `entry[].changes[].value.parent_id` | string | If this is a reply to another comment, the parent comment ID. |
+
+### Processing flow
+
+
+
+ The endpoint returns `200 OK` to Meta immediately. All processing happens asynchronously so Meta does not retry.
+
+
+ For each entry, NAWA looks up the `platform_connections` row where `platform = 'instagram'` and `platform_user_id` matches the entry's Instagram account ID.
+
+
+ The comment is upserted into the `comments` table with `platform = 'instagram'` and `processing_status = 'pending'`. Duplicate comments (same `platform_comment_id`) are handled via upsert.
+
+
+ NAWA invokes the `process-comment` edge function in a fire-and-forget pattern to run NAGL classification on the new comment.
+
+
+
+### Response
+
+The endpoint always returns `200` with body `ok` to prevent Meta from retrying. Processing errors are logged internally but do not affect the HTTP response.
+
+## Environment variables
+
+| Variable | Description |
+|----------|-------------|
+| `INSTAGRAM_WEBHOOK_VERIFY_TOKEN` | The verify token configured in your Meta app's webhook settings. Used for the GET handshake. |
+| `SUPABASE_URL` | Your Supabase project URL. |
+| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service role key for server-side database operations. |
+
+
+ If no active `platform_connections` row exists for the Instagram account ID in the webhook payload, the comment is silently dropped. Make sure the user has completed the Instagram OAuth flow before expecting webhook events.
+
diff --git a/docs.json b/docs.json
index 8f8f901..ca775c7 100644
--- a/docs.json
+++ b/docs.json
@@ -64,6 +64,16 @@
"api-reference/health",
"api-reference/usage"
]
+ },
+ {
+ "group": "Instagram Integration",
+ "pages": [
+ "api-reference/instagram-oauth",
+ "api-reference/instagram-webhook",
+ "api-reference/instagram-post-reply",
+ "api-reference/instagram-deauth",
+ "api-reference/instagram-data-deletion"
+ ]
}
]
},