Save this part
The short version
- Use an Instagram Professional account and choose either Instagram Login or Facebook Login; do not mix the two configurations.
- Treat the first automated DM as a Meta private reply with policy restrictions-not an unlimited outbound messaging channel.
- Verify the webhook signature against the raw body and make every delivery idempotent before calling Meta.
- Protect campaign data with authentication and Row Level Security; keep Meta and Supabase secrets server-only.
- Test with app-role accounts first, then complete the access, review, verification, privacy, and live-mode requirements for your intended users.
You can build comment-triggered Instagram DMs without paying for a dedicated automation subscription. You are still operating on Meta’s platform, within its permissions, messaging rules, rate limits, review process, and changing API versions-and your hosting and database providers still have usage quotas.
Prefer the 60-second version?Watch the original Instagram DM automation reel↗Architecture
What the Instagram automation does
A user comments a configured keyword on your Reel or post. Meta delivers a comment webhook to your Next.js route. Your server verifies the request, checks the keyword and media against an enabled campaign, claims the event so retries cannot duplicate it, sends one permitted private reply, and optionally posts a public acknowledgement only after the private reply succeeds.
- Meta Webhook: reports comment events to a public HTTPS endpoint.
- Next.js Route Handler: verifies, validates, matches, and coordinates each event.
- Supabase: stores authenticated campaigns and idempotent delivery outcomes.
- Instagram Messages API: sends the private reply associated with the comment.
- Instagram Comments API: optionally posts “Done-check your DMs” after success.
- Admin dashboard: creates and controls media-specific or account-wide keyword campaigns.
Do not publicly reply “check your DMs” before Meta confirms the private reply. Otherwise users receive a success message when delivery actually failed.
Read before building
Subscription-free does not mean unlimited or cost-free
The Instagram API with Instagram Login supports professional accounts-Instagram businesses and creators. Meta’s official collection uses the `instagram_business_basic`, `instagram_business_manage_comments`, and `instagram_business_manage_messages` permissions for this type of workflow.
A comment-triggered DM is a private reply. Meta documents policy restrictions around who can be contacted, when, and how follow-up messaging begins. API and product rules can change, so check the current documentation before launch rather than treating a copied endpoint or version as permanent.
- One private reply is allowed for a comment; it is not permission for an unlimited automated sequence.
- The documented private-reply window is seven days after a post or Reel comment; Instagram Live replies must be sent during the broadcast.
- If the commenter does not follow the professional account, the message may appear in Message Requests.
- Further automated messages depend on the person replying and the applicable messaging window and policy.
- Meta enforces API rate limits. Vercel and Supabase also enforce plan quotas.
- Using your own system removes a vendor subscription; it adds engineering, security, monitoring, policy, and maintenance responsibility.
Step 01
Choose one Meta authentication configuration
This guide uses Instagram API with Instagram Login and `graph.instagram.com`. Meta’s official collection states that this configuration does not require the professional Instagram account to be linked to a Facebook Page.
The older Instagram API with Facebook Login uses different permissions, tokens, prerequisites, and `graph.facebook.com` endpoints. A connected Facebook Page is required in that flow. Mixing instructions from both flows is a common cause of permissions and webhook failures.
- Use an Instagram Professional account: Business or Creator.
- For Instagram Login, request only the current `instagram_business_*` permissions the feature needs.
- Use the hostname, OAuth flow, token, and subscribed-app endpoint documented for the same configuration.
- Pin one currently supported Graph API version in an environment variable and schedule upgrades before it expires.
This guide intentionally does not require a Facebook Page because it follows Instagram Login. If you choose Facebook Login instead, follow that complete documentation from start to finish.
Step 02
Create the Meta app and connect a test account
In Meta for Developers, create an app and add the Instagram use case for managing messaging and comments. Dashboard labels change over time; verify each permission against the live permissions reference rather than relying only on screenshots.
- Configure the Instagram Login redirect URI and complete its OAuth setup.
- Add your professional Instagram account as an app role or tester while the app is in development.
- Request `instagram_business_basic`, `instagram_business_manage_comments`, and `instagram_business_manage_messages` for this workflow.
- Generate a token through the same Instagram Login configuration and record the professional account ID.
- Do not assume a development token is permanent. Monitor expiry and errors, and implement the documented refresh or reauthorization flow for your token type.
Step 03
Create or prepare the Next.js application
Use an existing Next.js App Router project or create a small TypeScript application. The dashboard and webhook can live in one project, but their trust boundaries must remain separate: users access campaigns through authenticated UI, while Meta accesses only the public webhook route.
npx create-next-app@latest instagram-dm-automation --ts --eslint --app
cd instagram-dm-automation
npm install @supabase/supabase-js @supabase/ssr zodnpm run devDo not expose a local development server directly to the internet without an authenticated or carefully scoped tunnel. Use a Vercel preview deployment for the first Meta callback when possible.
Step 04
Create secured campaign and delivery tables
The original single campaigns table is enough for a demo but not safe or reliable for a real webhook. Add ownership and Row Level Security for dashboard access, plus a server-only delivery table with a unique event key to prevent duplicate DMs when Meta retries.
-- Campaigns are managed by authenticated dashboard users.
create table public.instagram_campaigns (
id uuid primary key default gen_random_uuid(),
owner_id uuid not null references auth.users(id) on delete cascade,
media_id text,
reel_url text,
keyword text not null check (char_length(trim(keyword)) between 1 and 80),
dm_message text not null check (char_length(trim(dm_message)) between 1 and 1000),
public_reply text check (public_reply is null or char_length(trim(public_reply)) between 1 and 300),
enabled boolean not null default true,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create unique index instagram_campaign_owner_media_keyword_key
on public.instagram_campaigns (
owner_id,
coalesce(media_id, '*'),
lower(trim(keyword))
);
alter table public.instagram_campaigns enable row level security;
revoke all on table public.instagram_campaigns from anon, authenticated;
grant select, insert, update, delete on table public.instagram_campaigns to authenticated;
create policy "Owners can read campaigns"
on public.instagram_campaigns for select to authenticated
using ((select auth.uid()) = owner_id);
create policy "Owners can create campaigns"
on public.instagram_campaigns for insert to authenticated
with check ((select auth.uid()) = owner_id);
create policy "Owners can update campaigns"
on public.instagram_campaigns for update to authenticated
using ((select auth.uid()) = owner_id)
with check ((select auth.uid()) = owner_id);
create policy "Owners can delete campaigns"
on public.instagram_campaigns for delete to authenticated
using ((select auth.uid()) = owner_id);
-- Webhook delivery records make retries idempotent. Keep this table server-only.
create table public.instagram_delivery_events (
id uuid primary key default gen_random_uuid(),
event_key text not null unique,
comment_id text not null,
campaign_id uuid references public.instagram_campaigns(id) on delete set null,
status text not null check (status in ('processing', 'sent', 'ignored', 'failed')),
attempt_count integer not null default 1 check (attempt_count > 0),
error_code text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
alter table public.instagram_delivery_events enable row level security;
revoke all on table public.instagram_delivery_events from anon, authenticated;
-- Use a Supabase secret key only in trusted server code to access delivery records.
-- Do not add public policies to instagram_delivery_events.A Supabase secret key bypasses Row Level Security. Use it only in trusted server code after your own authorization checks-never in a browser bundle or a `NEXT_PUBLIC_` variable.
Step 05
Add server secrets and public Supabase configuration
Generate a long random webhook verification token. Keep it different from the Meta access token and app secret. Add local values to `.env.local`, confirm the file is ignored by Git, and add production values through Vercel Project Settings.
- `NEXT_PUBLIC_SUPABASE_URL` and the publishable key may be used by the authenticated browser client.
- The Meta app secret, Meta access token, webhook verify token, and Supabase secret key are server-only.
- Do not print access tokens, app secrets, signatures, or full webhook payloads in Vercel logs.
- After changing Vercel environment variables, redeploy; old deployments keep their old values.
openssl rand -hex 32git check-ignore .env.localMETA_APP_ID=
META_APP_SECRET=
META_VERIFY_TOKEN=
META_INSTAGRAM_ACCESS_TOKEN=
META_INSTAGRAM_ACCOUNT_ID=
META_GRAPH_API_VERSION=
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
SUPABASE_SECRET_KEY=Copy and build in stages
Four phased prompts for building the automation
These prompts follow the sequence used in the reel: foundation, webhook verification, comment-to-DM logic, and the campaign dashboard. They have been lightly hardened where the supplied MVP instructions conflicted with production security or reliability.
Commit and test after every phase. Do not paste all four into an empty conversation at once: each prompt assumes the previous phase works and gives the coding assistant a narrower surface to reason about.
You are a Staff Software Engineer at Vercel.
Your task is to build ONLY the foundation for an Instagram Comment → DM automation service.
This is Phase 1 of a larger project.
DO NOT implement any Instagram logic yet.
DO NOT connect to Meta.
DO NOT connect to Supabase.
DO NOT add authentication.
DO NOT add Redis.
DO NOT add BullMQ.
DO NOT add Prisma.
DO NOT add Docker.
DO NOT add any unnecessary packages.
The only goal is to produce a clean, production-ready Next.js application that can be deployed to Vercel and is ready to become a Meta webhook receiver.
=========================================================
PROJECT
=========================================================
Project name:
instagram-comment-bot
Tech stack:
• Next.js 15+
• App Router
• TypeScript
• Tailwind CSS
• ESLint
• npm
Use the latest stable versions.
=========================================================
PROJECT GOALS
=========================================================
After completing this task I should be able to:
✅ Run locally
npm run dev
✅ Build successfully
npm run build
✅ Deploy to Vercel with zero changes
✅ Visit the homepage
✅ Visit the webhook endpoint
Nothing else.
=========================================================
PROJECT STRUCTURE
=========================================================
Use a clean architecture.
app/
api/
webhooks/
meta/
route.ts
lib/
config/
types/
public/
Do not create unnecessary folders.
=========================================================
HOMEPAGE
=========================================================
Create a clean homepage.
Title:
Instagram Comment Bot
Subtitle:
A lightweight webhook receiver for Instagram Comment → DM automation.
Display:
----------------------------
Instagram Comment Bot
Status
🟢 Ready
This project is currently running.
Current Phase
Foundation & Webhook Setup
----------------------------
No buttons.
No forms.
No authentication.
No dashboard.
No animations.
No unnecessary UI.
Keep it simple and professional.
=========================================================
WEBHOOK ROUTE
=========================================================
Create:
app/api/webhooks/meta/route.ts
Implement BOTH handlers.
GET
Return HTTP 200
Response:
{
"status": "ok",
"message": "Meta webhook endpoint is ready.",
"phase": "foundation"
}
POST
Return HTTP 200
Response:
{
"status": "received",
"message": "Webhook request received.",
"phase": "foundation"
}
Do NOT:
• verify tokens
• parse request body
• log payload
• save to database
Those belong to future phases.
=========================================================
CONFIG
=========================================================
Create an .env.example
Include only:
WEBHOOK_VERIFY_TOKEN=
Explain in comments that it will be used in the next phase.
Do not use it yet.
=========================================================
README
=========================================================
Generate a professional README.
Include:
• Project overview
• Tech stack
• Folder structure
• How to install
• How to run
• How to build
• How to deploy to Vercel
• Project roadmap
Roadmap should include:
Phase 1
✓ Foundation
Phase 2
Webhook Verification
Phase 3
Receive Comment Events
Phase 4
Keyword Matching
Phase 5
Send Instagram DM
Phase 6
Logging & Dashboard
=========================================================
CODE QUALITY
=========================================================
Requirements:
• strict TypeScript
• zero TypeScript errors
• zero ESLint warnings
• clean imports
• reusable architecture
• meaningful comments
• production-ready formatting
No placeholder junk.
No dead code.
No unused dependencies.
=========================================================
TESTING
=========================================================
Before finishing, run:
npm install
npm run lint
npm run build
Fix every issue until everything passes.
=========================================================
OUTPUT
=========================================================
After generating the project:
1. Explain the folder structure.
2. Explain every file.
3. Explain how Next.js API routes work.
4. Explain how GET requests differ from POST requests.
5. Explain how this webhook endpoint will later connect to Meta.
6. Provide the exact commands to:
- install dependencies
- run locally
- initialize a Git repository
- create the first commit
- deploy to Vercel using the Vercel CLI
Do not implement anything beyond the scope of this prompt.
The application should be production-ready and immediately deployable to Vercel.You are continuing the existing Next.js project from the previous phase.
DO NOT modify the existing project architecture.
DO NOT add authentication.
DO NOT add a database.
DO NOT add Supabase.
DO NOT add Redis.
DO NOT add BullMQ.
DO NOT add any Instagram business logic.
DO NOT send DMs.
DO NOT match keywords.
Your ONLY goal is to complete the next two commits.
=========================================================
PROJECT CONTEXT
=========================================================
The project already contains:
- Next.js 15
- App Router
- TypeScript
- Tailwind
- Homepage
- /api/webhooks/meta endpoint
- Successfully deployed to Vercel
We are now making the endpoint compatible with Meta Webhooks.
=========================================================
COMMIT 3 — META WEBHOOK VERIFICATION
=========================================================
Implement Meta's webhook verification flow.
Environment variable:
WEBHOOK_VERIFY_TOKEN
The GET endpoint must support these query parameters exactly as Meta sends them:
- hub.mode
- hub.verify_token
- hub.challenge
Logic:
1. Read hub.mode.
2. Read hub.verify_token.
3. Read hub.challenge.
4. If hub.mode == "subscribe" AND hub.verify_token == process.env.WEBHOOK_VERIFY_TOKEN, return HTTP 200 with hub.challenge.
5. Return ONLY the raw challenge string-not JSON or HTML.
6. If verification fails, return HTTP 403 with body: Forbidden
Validation example:
GET /api/webhooks/meta?hub.mode=subscribe&hub.verify_token=test123&hub.challenge=987654321
Expected response:
987654321
Status: 200
=========================================================
COMMIT 4 — RECEIVE WEBHOOK EVENTS
=========================================================
Implement POST support.
This endpoint must accept webhook events from Meta.
Requirements:
- Read the request body safely.
- Parse the JSON.
- For this temporary development phase only, pretty-print the payload to the server console using console.log(JSON.stringify(payload, null, 2)).
- Return immediately.
- Do NOT perform processing.
- Do NOT extract fields.
- Do NOT save anything.
- Do NOT send DMs.
- Do NOT verify signatures yet.
Return HTTP 200 JSON:
{
"status": "ok",
"message": "Webhook received."
}
=========================================================
ERROR HANDLING
=========================================================
If the request body cannot be parsed, return HTTP 400:
{
"error": "Invalid JSON"
}
If an unexpected error occurs, return HTTP 500:
{
"error": "Internal Server Error"
}
=========================================================
ENVIRONMENT
=========================================================
Update .env.example with:
WEBHOOK_VERIFY_TOKEN=replace_me_with_random_string
Add comments explaining:
- this value is chosen by us
- Meta never generates it
- the exact same value will later be entered into the Meta Developer Dashboard
=========================================================
CODE QUALITY
=========================================================
Requirements:
- Strict TypeScript
- No any types
- No ESLint warnings
- Reusable helper functions if appropriate
- Clean error handling
- Meaningful comments
- No dead code
- No unnecessary abstractions
=========================================================
README
=========================================================
Add a Webhook Verification section.
Explain:
- what a webhook is
- why Meta verifies ownership
- what the callback URL is
- what the verify token is
- why Meta sends a GET request first
- why it later sends POST requests
Explain the flow:
User comments
↓
Instagram
↓
Meta Webhook
↓
Our endpoint
=========================================================
LOCAL TESTING
=========================================================
Add instructions for:
- GET verification using the browser
- GET verification using curl
- POST requests using curl
- POST requests using Postman
- what should appear in the terminal
=========================================================
OUTPUT
=========================================================
After implementing everything, explain:
1. How Meta verifies ownership.
2. Why the verify token exists.
3. Why the challenge must be returned exactly.
4. How POST requests differ from GET requests.
5. Show an example Instagram webhook payload and explain every field.
6. Explain how keyword matching and DMs will build on this code.
Do not implement anything beyond this scope.
Important: this phase deliberately omits POST signature verification and logs full payloads for temporary debugging. Do not expose it to production traffic. Remove full-payload logging and add X-Hub-Signature-256 validation before enabling real automation.You are a senior Next.js + TypeScript engineer.
I already have a working Meta Webhook integrated with my Next.js application. Instagram comments and messages are successfully reaching my webhook endpoint.
Your task is to implement the next stage of the automation.
Tech stack:
- Next.js App Router
- TypeScript
- Supabase
- Vercel
- Official Meta Graph API
- Existing Supabase client utilities (reuse them)
- Existing environment variable structure (reuse it)
Do not introduce new libraries unless absolutely necessary.
Implement both GET and POST handlers of the webhook route. Keep the route small and separate business logic into helper or service files.
WEBHOOK VERIFICATION
- Read hub.mode, hub.verify_token, and hub.challenge.
- Compare the verify token with the environment variable.
- Return the raw challenge when verification succeeds.
- Return HTTP 403 when it fails.
POST EVENT FLOW
- Validate X-Hub-Signature-256 against the exact raw request body with META_APP_SECRET before parsing JSON.
- Validate the webhook payload.
- Ignore unsupported event types and return HTTP 200 for them.
- Extract comment ID, comment text, Instagram user ID when available, username when available, and media ID in a dedicated parser.
- Normalize the comment with Unicode normalization, trim, and locale-independent lowercase.
- Use exact keyword matching only; do not use fuzzy or substring matching.
SUPABASE CAMPAIGN LOOKUP
Use the existing campaigns table and types. Find one campaign where:
- enabled = true
- normalized keyword exactly matches the normalized comment
- media ID matches when the campaign is scoped to specific media
- campaign belongs to the receiving Instagram account or authenticated owner
If no campaign exists, return HTTP 200 without further work.
PRIVATE REPLY
If a campaign exists, send an Instagram private reply using the comment ID and the documented private-reply form of the official Meta Graph API. Use campaign.message as the message content.
Implement this in a reusable server-only service such as sendInstagramPrivateReply(). Do not put Graph API code directly in the route.
PUBLIC COMMENT REPLY
Only after the private reply succeeds, optionally reply publicly to the original Instagram comment with:
Done! Check your DMs 📩
Implement this as a reusable function.
If the private reply fails, record and log a redacted error. Do not post a misleading success comment. Do not publicly expose internal failures unless the campaign explicitly configures a safe fallback reply.
IDEMPOTENCY AND ERRORS
- Claim a stable unique event key before calling Meta so webhook retries cannot send duplicate DMs.
- Treat duplicate and unsupported deliveries as successful no-ops.
- Log meaningful, redacted errors.
- Never expose internal errors or secrets in the HTTP response.
- Return the correct acknowledgement strategy for verified events, but do not silently discard failures that require a controlled retry.
MODULES
Use logical modules similar to:
app/api/webhooks/meta/route.ts
lib/instagram/webhook-parser.ts
lib/instagram/meta-client.ts
lib/instagram/send-private-reply.ts
lib/instagram/reply-to-comment.ts
lib/services/campaigns.ts
lib/services/delivery-events.ts
The exact names may differ, but keep responsibilities separate.
CODE QUALITY
- TypeScript only
- Strong typing
- No any
- Async/await
- Small reusable functions
- Good naming
- Comments only where useful
- Avoid duplicated code
- Follow Next.js App Router conventions
- Keep Meta tokens, app secrets, and Supabase secret keys server-only
Do not add analytics, Redis, BullMQ, background workers, or cron jobs in this phase.
Return production-quality code and explain the Meta endpoints, permissions, environment variables, tests, remaining dashboard steps, and any limits of inline processing.You are a senior frontend engineer specializing in Next.js, TypeScript, Tailwind CSS, shadcn/ui and Supabase.
I already have an Instagram automation backend that:
- receives Instagram webhook events
- looks up campaigns in Supabase
- sends Instagram private replies
- replies to Instagram comments
Authentication and campaign Row Level Security already exist. Reuse them. Do not expose the dashboard or database mutations without server-enforced authorization.
Your task is to build a polished internal admin dashboard to manage campaigns using the existing project architecture and design system. Do not introduce unnecessary dependencies.
GOAL
Users should be able to:
- view campaigns they own
- search campaigns
- create campaigns
- edit campaigns
- delete campaigns
- enable or disable campaigns
Use the existing Supabase client utilities and types.
DATABASE
Use the existing campaigns table. Expected fields include:
- id
- owner_id
- media_id when supported
- reel_url when supported
- keyword
- message or dm_message
- public_reply when supported
- enabled
- created_at
- updated_at
Inspect the actual migration and generated types before coding. Do not invent a second schema or client.
PAGE
Create:
/dashboard/instagram
Use the existing authenticated dashboard layout.
Top section:
- page title
- short description
- New Campaign button
Below it, show a searchable campaign table with:
- Keyword
- Media scope
- Message Preview
- Status
- Created At
- Actions
Status badges:
- Green: Enabled
- Gray: Disabled
Message previews show the first 60 characters and end with ... when longer. A tooltip should expose the full message.
Actions include Edit and Delete, preferably using existing icons and accessible tooltips.
SEARCH
Add instant client-side keyword filtering over the already loaded owner-scoped campaigns.
EMPTY STATE
Show:
Instagram Campaigns
No campaigns yet.
Create your first automation to automatically send DMs when users comment specific keywords.
Include a prominent Create Campaign button.
CREATE AND EDIT
Use one responsive dialog for both operations.
Fields:
- Keyword: required, single-line, placeholder "guide"
- Message: required, multiline
- Media ID or Reel URL when the existing schema supports media scoping
- Optional public acknowledgement when supported
- Enabled: switch
Trim inputs and normalize the keyword consistently with the webhook matcher before saving.
Buttons:
- Cancel
- Create Campaign or Save Changes
Close after success and update local state immediately.
DELETE
Never delete immediately. Open an accessible confirmation dialog:
Are you sure you want to delete this campaign?
This action cannot be undone.
Buttons:
- Cancel
- Delete Campaign
Refresh local state after success.
ENABLE OR DISABLE
Allow status changes from the table using a Switch. Update Supabase immediately and revert optimistic state if the request fails.
UX
- Use optimistic UI where safe.
- Disable buttons while requests are pending.
- Prevent duplicate submissions.
- Show loading states and skeletons.
- Show success toasts for created, updated, deleted, enabled, and disabled states.
- Show meaningful, non-sensitive error toasts.
- Preserve keyboard navigation, visible focus, labels, and accessible dialog behavior.
COMPONENTS
Split the feature into focused components where useful, for example:
components/instagram/campaign-table.tsx
components/instagram/campaign-row.tsx
components/instagram/campaign-form-dialog.tsx
components/instagram/delete-dialog.tsx
components/instagram/status-switch.tsx
components/instagram/search-bar.tsx
Avoid large files and unnecessary abstractions.
DATA LAYER
Keep database logic outside visual components with functions such as:
- getCampaigns()
- createCampaign()
- updateCampaign()
- deleteCampaign()
- toggleCampaign()
Every mutation must verify the current user and constrain the operation by owner ID on the server. RLS remains enabled as defense in depth.
UI
Use existing shadcn/ui components when they are already installed, such as Card, Table, Dialog, Alert Dialog, Input, Textarea, Badge, Switch, Button, Tooltip, Dropdown Menu, and Skeleton.
Do not install the entire component library. Add only missing components that are necessary and consistent with the existing project.
The design should feel calm and polished, with generous spacing, rounded corners, subtle shadows, responsive dialogs, and a horizontally scrollable table when needed on mobile.
PERFORMANCE
- Fetch campaigns once on page load.
- Avoid unnecessary refetches.
- Update local state after successful CRUD operations.
- Do not leak campaigns between users through caching.
FUTURE EXTENSIBILITY
Keep the model and layout ready for future fields such as match type, delay, DM template, tags, analytics, and active hours. Do not implement them now.
CODE QUALITY AND TESTING
- TypeScript
- Server Components where appropriate
- Client Components only for interaction
- Strong typing
- Clean folder structure
- No duplicated logic
- Tests for authorization boundaries, validation, normalization, failed optimistic updates, and CRUD behavior
- Run lint, typecheck, tests, and production build
Return production-quality code plus a list of files changed, architectural decisions, security assumptions, environment requirements, and manual test steps.Phase 2 is a temporary learning step: full payload logging can collect personal data, and an unsigned POST endpoint can be forged. Do not connect production traffic until signature validation and redacted logging are implemented.
Production alternative
Use this one-shot prompt for an existing application
If the project already has authentication, Supabase, and an established design system, use this consolidated prompt instead of rebuilding the feature through four tutorial phases. Paste it into Cursor, Claude Code, Codex, Windsurf, or another coding assistant while the project is open.
Review every change, run the tests, and verify the generated Meta calls against the current documentation before deployment.
Act as a senior full-stack engineer. Build a secure Instagram comment-to-DM automation dashboard in my existing Next.js App Router project using TypeScript, Supabase, Vercel, and Meta's Instagram API with Instagram Login.
Before editing code:
1. Inspect the project structure and existing auth, Supabase clients, validation, styling, tests, and conventions.
2. Explain the smallest implementation plan.
3. Do not mix Instagram Login with Facebook Login APIs. Use graph.instagram.com and the current instagram_business_* scopes for this implementation.
4. Ask before making an architectural change outside this feature.
FUNCTIONAL REQUIREMENTS
- Add an authenticated admin dashboard for campaigns.
- A campaign contains: optional Instagram media ID, optional Reel URL, keyword, DM message, optional public comment reply, enabled state, owner, and timestamps.
- Allow authenticated owners to create, edit, enable/disable, and delete only their campaigns.
- Normalize incoming comments with Unicode normalization, trim, and locale-independent lowercase.
- Default to an exact normalized keyword match. Do not use substring matching unless it is explicitly enabled later.
- If media_id is present, require the webhook media ID to match it.
- Ignore comments created by the professional account itself.
WEBHOOK ROUTE
- Create app/api/webhooks/meta/route.ts using the Node.js runtime.
- GET: verify hub.mode, hub.verify_token, and hub.challenge using META_VERIFY_TOKEN. Return the challenge only when verification succeeds; otherwise return 403.
- POST: read the raw request body exactly once with request.text().
- Validate X-Hub-Signature-256 using HMAC-SHA256 with META_APP_SECRET and a constant-time comparison before parsing JSON.
- Validate the payload shape. Never trust webhook fields blindly.
- Extract each Instagram comment event safely. Handle batched entries and changes.
- Build a stable event key from immutable event fields and claim it in instagram_delivery_events before making Meta API calls.
- Duplicate deliveries must return 200 without sending another DM or comment reply.
- Find one enabled campaign by normalized keyword, owner/account, and media ID when configured.
- Send one private reply using the comment ID through the documented private-reply form of the Instagram Messages endpoint.
- Only after the DM succeeds, optionally publish the configured public reply to the comment.
- Store sent, ignored, and failed outcomes without storing unnecessary commenter profile data or full webhook payloads.
- Return a successful acknowledgement for valid duplicate or ignored events. Use structured, redacted server logs.
META CLIENT
- Create a small server-only Meta client.
- Read the API version from META_GRAPH_API_VERSION; do not hardcode a version throughout the codebase.
- Use Authorization: Bearer rather than putting access tokens in URLs or logs.
- Add request timeouts and parse Meta error responses safely.
- Distinguish retryable errors such as timeouts and 5xx responses from permanent permission, policy, or validation errors.
- Never expose the app secret, access token, Supabase secret key, or raw webhook payload to the browser.
SUPABASE AND AUTH
- Reuse existing Supabase clients and authentication if present; do not create duplicates.
- Use the publishable key in the browser with Row Level Security.
- Use a separate server-only client with SUPABASE_SECRET_KEY only where elevated webhook access is required.
- Protect every dashboard route and mutation on the server. Hiding controls in the UI is not authorization.
- Validate all campaign input with the project's existing validation approach.
UX
- Match the existing design system exactly.
- Show empty, loading, success, and error states.
- Confirm destructive campaign deletion.
- Display whether a campaign applies to one media ID or all owned media.
- Explain that a private reply is limited by Meta policy and that further automated messages require user interaction and an allowed messaging window.
RELIABILITY
- Add tests for GET verification, invalid signatures, malformed payloads, exact keyword matching, media matching, self-comments, duplicate deliveries, Meta failure, DM success, and optional public reply behavior.
- Keep webhook processing idempotent.
- For an MVP, process inline and return promptly. Document when to move work to a durable queue; do not pretend a serverless fire-and-forget promise is reliable.
AFTER IMPLEMENTATION
- List every file created or changed.
- List environment variables without revealing values.
- List remaining Meta Dashboard, App Review, Supabase, and Vercel steps.
- Provide local and production verification steps.
- Note assumptions and security limitations.
Keep changes minimal, production-minded, and consistent with the existing codebase.Critical implementation detail
Verify both the callback token and every POST signature
Meta’s GET challenge proves that you control the callback URL. It does not authenticate later webhook deliveries. For POST requests, read the raw body once and verify `X-Hub-Signature-256` with HMAC-SHA256 using the Meta app secret before parsing or processing the JSON.
Use a constant-time comparison and reject missing or invalid signatures. Validate the JSON shape after signature verification because a correctly signed payload may still omit fields your code expects.
- GET: compare `hub.verify_token` with `META_VERIFY_TOKEN`, then echo `hub.challenge`.
- POST: compute the signature over the exact raw bytes Meta sent.
- Never parse and re-serialize the payload before signature verification.
- Never use the verification token as the HMAC secret; use `META_APP_SECRET`.
- Return quickly and design for Meta to retry delivery.
Step 07
Deploy and configure the Meta webhook
Deploy the application to a stable public HTTPS URL. In the Meta app dashboard, set the callback URL to your route and enter the exact same verification token stored in Vercel. Subscribe to the comment field required by the current Instagram webhook configuration. Subscribe to messaging fields only if the application actually processes replies or postbacks.
- Callback URL: `https://your-domain.com/api/webhooks/meta`.
- Confirm the GET verification returns the challenge with HTTP 200.
- Complete the account-level subscribed-app step documented for Instagram Login; dashboard field selection alone may not complete delivery setup.
- Comment from a different eligible Instagram account on media owned by the professional account.
- Inspect redacted Vercel Function logs for a verified event and delivery status.
Step 08
Test the complete flow before enabling a campaign
- Create one disabled campaign and confirm an exact normalized keyword does not trigger it.
- Enable it, then comment from a different account on the configured Reel or post.
- Confirm one verified webhook event is received and matched to the correct media ID.
- Confirm exactly one private reply appears in Inbox or Message Requests.
- Confirm the public acknowledgement is posted only after private-reply success.
- Replay the same signed fixture in a test environment and confirm no second message is sent.
- Test an uppercase keyword, extra surrounding whitespace, a near match, an unrelated media ID, and a comment from the professional account itself.
- Test expired, revoked, and under-permissioned tokens so the dashboard exposes an actionable failure instead of silently dropping leads.
Step 09
Understand development mode, Live mode, and App Review
While the Meta app is in development, test with accounts that have the required app and Instagram roles. That is different from making the integration available to arbitrary professional accounts or real users outside the app roles.
Before broader use, review Meta’s current requirements for Live mode, permission access level, App Review, business verification, privacy policy, data deletion instructions, and screencast evidence. The exact review path depends on whether this is only for your own managed account or a product that connects other customers’ accounts.
Do not publish the app merely because a tutorial says webhooks require it. Prove the flow with test roles first, then request only the production access your real use case requires.
Before real volume
Add reliability before a high-traffic Reel
An inline serverless handler can be acceptable for a low-volume MVP, but it must remain idempotent and finish within the platform’s delivery expectations. A large comment spike needs a durable queue or job system so acknowledgement and processing can be separated safely.
- Unique event claims to prevent duplicate private replies.
- Bounded retries with exponential backoff and jitter for retryable Meta errors.
- A dead-letter or failed-delivery view with safe manual replay.
- Rate-limit awareness and backpressure instead of uncontrolled parallel calls.
- Token-expiry and permission-error alerts.
- Metrics for received, matched, ignored, sent, failed, retried, and duplicate events.
- Retention limits for webhook and delivery data; do not collect commenter data you do not need.
- A clear privacy policy and a way to handle access or deletion requests where applicable.
Common failures
Instagram webhook and DM troubleshooting checklist
- Webhook verification fails: compare the exact verification token, query parameter names, deployed URL, and production environment values.
- POST returns 401: confirm the raw body is used and `X-Hub-Signature-256` is checked with the Meta app secret.
- No comment events: verify app roles, webhook field subscription, account-level subscribed-app status, owned media, and that the comment came from another account.
- Comment arrives but no campaign matches: inspect normalized text, enabled state, owner/account mapping, and media ID.
- Public reply works but DM fails: verify the messaging permission and the private-reply payload; do not substitute a normal outbound text-message flow.
- Requests hit the wrong host: confirm every token and endpoint comes from the same Instagram Login configuration.
- Production still uses old secrets: redeploy after changing Vercel environment variables.
- Duplicate DMs: claim a stable unique event before calling Meta, and treat duplicate webhook deliveries as successful no-ops.
- Works only for the developer: determine whether the intended accounts require Live mode, advanced access, App Review, or business verification.
Common questions
Instagram comment-to-DM automation FAQ
Can I build Instagram comment-to-DM automation for free?
You can avoid a dedicated automation-tool subscription and may fit within the free tiers of your hosting and database providers. It is not guaranteed to cost zero forever: Meta enforces API and messaging limits, infrastructure plans have quotas, and you are responsible for development, maintenance, monitoring, and compliance.
Do I need a Facebook Page for Instagram DM automation?
Not when you use Instagram API with Instagram Login; Meta’s official collection states that this configuration does not require a linked Facebook Page. The Instagram API with Facebook Login is a different configuration and does require the appropriate Facebook Page relationship. Do not mix their instructions.
Can the automation send unlimited DMs to everyone who comments?
No. Meta treats the first comment-triggered message as a private reply and restricts how and when it can be sent. Further messaging depends on the person interacting and the applicable messaging policy and window. API rate limits also apply.
Why do I need webhook idempotency?
Webhook providers retry events when delivery or acknowledgement is uncertain. Without a unique event claim, the same Instagram comment can trigger multiple DMs or public replies. Store a stable event key before making external calls and make duplicate deliveries successful no-ops.
Should I store the Instagram access token in Supabase?
For a single-account project, a protected server environment variable is simpler. Never expose the token to the browser. A multi-account product may need encrypted token storage, strict tenant isolation, rotation, revocation handling, and carefully audited server-only access.
Do I need App Review to test the automation?
You can normally develop with accounts assigned the required app and Instagram roles. Access for accounts or people outside those roles can require Live mode, advanced permission access, App Review, business verification, and policy disclosures. Check Meta’s current requirements for your exact distribution model.
Found something worth remembering?
Save the useful idea in Offload with the context that made it matter. Ask for it naturally when you’re ready to use it.
Try Offload free