Anwar Bouilouta

Build Your Own ManyChat With Claude Code and Free Cloudflare

Comment-to-DM on your own Instagram, running on a free Cloudflare account. One prompt builds it and these seven steps get it live.

A free guide by Anwar Bouilouta 13 min read

Back to all free guides

So this is everything from the video: the prompt, the seven steps and what to do once it's running. Someone comments a word on your post, they get a public reply and a DM with a button, and when they tap it you send them your link.

Mine's been running since 13 October on a free Cloudflare account. It's answered over 1,300 comments and sent over 3,000 DMs, and the bill is still zero.

What you need

  • A public Instagram account set to professional
  • Claude Code, which comes with the $20 a month Claude plan
  • A free Cloudflare account
  • A free Meta developer account
  • A second Instagram account to test from

Step 1: Make your Cloudflare account

Sign up at cloudflare.com. The free plan is all you need, and the prompt is written to stay inside its limits.

Step 2: Paste the prompt into Claude Code

Make an empty folder, open Claude Code in it, and copy the prompt below. Fill in the short form at the top with your Instagram username, the word people comment, the DM you want to send and your link, then paste the whole thing in.

Claude writes the worker and tests it against a fake version of Instagram before it touches your account. After that it asks you to log into Cloudflare once in your browser, creates the database and puts the worker live.

At the end you get a web address and a checklist. That address is where Instagram sends every comment, so keep it handy for the next steps.

The prompt's long, so press Copy on the box below and it'll grab the whole thing.

The prompt
You are building my own Instagram comment-to-DM system, so I can stop paying for one. Someone comments a keyword on my post. They get a public reply under the comment and a DM with a button. They tap it, and if they follow me, they get my link. It runs on a free Cloudflare Workers account and Instagram's official API. Open this in an empty folder in Claude Code and build the whole thing.

## My setup

Fill this in. Leave the example keyword in if you want to test first.

```
My Instagram username:
My name, for the privacy page:
My email, for the privacy page:
Worker name (lower case, for example my-dms):

Keyword 1:
  Word they comment: GUIDE
  First DM: Here's the guide you asked for. Tap below and I'll send it over.
  Button label (20 characters at most): Send me the link
  Must they follow me to get the link? (yes or no): yes
  Link message: Here you go.
  Link: https://example.com/guide
  Link button label (20 characters at most): Open the guide
  Message when they don't follow yet: This one's just for people who follow me, so give me a follow, then tap below.
  Follow button label (20 characters at most): I've followed
```

Add a block for each extra keyword.

## What to build

Plain JavaScript on Cloudflare Workers, no framework, with a D1 database and one KV namespace.

- `wrangler.jsonc`: the worker, the D1 and KV bindings, vars `GRAPH_BASE` = `https://graph.instagram.com`, `IG_API_VERSION` = `v25.0`, `IG_USERNAME`, `IG_ACCOUNT_ID` (empty until I give it to you), `OWNER_NAME`, `OWNER_EMAIL`, and crons `*/15 * * * *` and `17 6 * * 1`.
- `package.json`: wrangler as the only dev dependency.
- `keywords.json`: every keyword and its wording, so I can add one by editing a single file.
- `migrations/0001_init.sql`: tables `contacts` (one row per person: id, username, keyword, stage, first seen, last tap), `events` (a timeline with a comment id and no message text), `seen` (handled comment, tap and message ids). Put an index on every column a query filters by.
- `src/worker.js`, split into more files if that reads better.
- `test/smoke.mjs`: runs the logic below against a fake Instagram API and in-memory stand-ins for D1 and KV, with no network, and prints `SMOKE OK` only when every case passes.

Routes:

- `GET /webhook`: Meta's handshake. If `hub.mode` is `subscribe` and `hub.verify_token` matches `VERIFY_TOKEN`, answer with `hub.challenge` as plain text. Otherwise 403.
- `POST /webhook`: check `X-Hub-Signature-256` against the raw body with HMAC SHA-256 and `IG_APP_SECRET`, in constant time, and answer 401 if it's wrong. Then answer 200 straight away and do the work inside `ctx.waitUntil`, because Meta retries anything slow.
- `GET /health`: which secrets are set (true or false, never the values), whether Instagram accepted the token at the last check, whether the account is subscribed, and the last cron run, from KV. It must not count a table.
- `GET /privacy` and `GET /data-deletion`: short plain pages using my name and email. Meta needs these two URLs before the app can be published.
- Anything else: 404.

## The flow

Every Instagram call goes to `{GRAPH_BASE}/{IG_API_VERSION}`, apart from the token refresh, and sends `Authorization: Bearer <token>`. Every POST with a body sends JSON.

Only act on payloads where `object` is `instagram`. While `IG_ACCOUNT_ID` is empty, ignore comments and taps without claiming them. Each `entry` can hold `messaging` events and comment changes. Meta sends comments in two shapes: inside `entry.changes[]`, and as `field` and `value` directly on the entry. Handle both, and only where `field` is `comments`.

**A comment** (`value.id`, `value.text`, `value.from.id`, `value.from.username`, `value.media.id`):

1. Skip the account's own comments, by id and by username. My public reply comes back as a comment webhook, and it would loop.
2. Match a keyword as a whole word, ignoring case, so GUIDE doesn't fire on GUIDELINES.
3. Claim the comment id in `seen` with an atomic insert, so Meta's retries never send twice.
4. Send the DM: `POST {GRAPH_BASE}/{IG_API_VERSION}/{account id}/messages` with `recipient: { comment_id }` and a button template holding a `postback` button whose payload names the keyword.
5. If the DM fails, delete the `seen` row and record the failure as an event, so the sweep can try again. After 3 failures, keep the `seen` row, so one bad comment never uses up a run.
6. Only after the DM has gone, post the public reply to `POST /{comment id}/replies` with a `message` field. Keep ten short variations of "Sent it over, check your DMs and your message requests" and pick one by a hash of the comment id, so they don't all read the same. Non-followers find the DM in message requests, so the reply has to say so.
7. Save the contact and the event after the DM, so the records never slow it down.

**A tap** (`messaging[].postback.payload`, with `sender.id`):

1. Claim the message id (`postback.mid`) in `seen`.
2. If the keyword doesn't need a follow, send the link as a button template with a `web_url` button.
3. Otherwise read `GET /{sender id}?fields=is_user_follow_business`. Instagram only answers this after someone has tapped a button or messaged me. Treat true or no answer as a yes, so a real follower never gets stuck, and send the link.
4. If it's false, send the follow message with a postback button that runs this same tap again. Allow only one prompt per person per 15 seconds, using a sliding window on the contact's last tap time, so double taps don't send two.

Ignore `message.is_echo` events, because those are my own messages coming back. Ignore typed messages too, so they stay in my normal inbox.

**The 15-minute cron:** check the token with `GET /me` and record the result and the run in KV. If the token works, `IG_ACCOUNT_ID` is set and KV doesn't say the account is subscribed yet, call `POST /me/subscribed_apps?subscribed_fields=comments,messages,messaging_postbacks` and record that in KV. Then, if the token works and `IG_ACCOUNT_ID` is set, run the sweep.

**The sweep** answers any keyword comment the webhook missed. Save the time of the first cron run with a working token and an account id in KV, and only answer comments newer than that, so nobody who commented before this system existed gets a surprise DM. Read `/me/media?fields=id,timestamp,comments_count&limit=20`, skip posts whose `comments_count` matches the number saved in KV, and read the rest with `/{media id}/comments?fields=id,text,timestamp,from&limit=50`. Follow `paging.next` only if it starts with `GRAPH_BASE` plus a slash, so the token never goes anywhere else. Send each comment that's under 7 days old, with an hour spare, through the same handler. Stop at 15 reads, counting the media read, or 6 DMs in one run. Count every fetch, KV call and D1 call in the run too, and stop at 40. Leave a post's count unsaved if the run stopped on that post or a DM failed on it. If a run stops partway through a post's pages, save the link of the page it stopped on in KV, along with whether a DM failed, and start there next run, so a big post never restarts from page one.

**The weekly cron:** refresh the token with `GET {GRAPH_BASE}/refresh_access_token?grant_type=ig_refresh_token&access_token=...` and store the new one in KV. Always use the KV token if there is one, and fall back to the `IG_TOKEN` secret. Tokens last 60 days, and one that's left to expire can't be refreshed.

## Instagram's rules

- One message per comment, within 7 days of the comment. Anything more waits for a tap or a message.
- After a tap or message, you've got 24 hours to message that person.
- Message text is 1,000 bytes at most. A button template is 640 characters at most, and a button title is 20.
- The account must be a public professional account.

## Stay on the free plan

- The Workers free plan gives 100,000 requests a day, 50 subrequests per run and 5 cron triggers. KV and D1 calls count as subrequests alongside fetches, so each webhook run handles taps first and stops at 40, before claiming the next comment, and the sweep picks up the rest. Each run gets 10 ms of CPU, and waiting on the network doesn't count, so keep cron work to network calls and small loops. D1 gives 5 million rows read and 100,000 written a day. KV gives 1,000 writes a day, so write to KV only when a value changes.
- Never run a query on a schedule that scans a whole table. Check any query that runs on a timer against its index.

## Secrets

`IG_TOKEN`, `IG_APP_SECRET` and `VERIFY_TOKEN` are Worker secrets, set with `npx wrangler secret put`. Never write them to a file, never print them, and never ask me to paste one into this chat. Tell me the command and I'll paste the value into the terminal. Generate `VERIFY_TOKEN` yourself with a random generator and set it without showing it to me until the webhook step, where I need to type it into Meta's form.

## How to run it

1. Build everything, run `npm install` and `node test/smoke.mjs`, and fix it until it prints `SMOKE OK`.
2. Check I'm logged in with `npx wrangler whoami`. If I'm not, run `npx wrangler login` and wait.
3. Create the D1 database and the KV namespace, put their ids in `wrangler.jsonc`, apply the migration with `--remote`, set `VERIFY_TOKEN`, and deploy.
4. Give me a numbered checklist with my real worker URL filled in, covering what I do in Meta's dashboard: create an app at developers.facebook.com with the use case "Manage messaging & content on Instagram", open API setup with Instagram login, add my account, generate a token, run `npx wrangler secret put IG_TOKEN` and `npx wrangler secret put IG_APP_SECRET`, send you my Instagram account id, then the callback URL `<worker URL>/webhook`, the verify token, the webhook fields `comments`, `messages` and `messaging_postbacks`, the privacy URL `<worker URL>/privacy`, the data deletion URL `<worker URL>/data-deletion`, and pressing Publish. Remind me to turn on Allow Access to Messages under Connected Tools in Instagram's message settings.
5. When I've given you my Instagram account id, put it in the config and deploy again. The next cron run subscribes the account, so check `/health` until it says so. That takes up to 15 minutes.
6. Tell me to comment the keyword from a second account, because the worker skips my own comments. Watch `npx wrangler tail` while I do it, and fix whatever it shows. If the comment never reaches the webhook, tell me Meta may want Advanced Access for comments, and that the sweep still answers it within 15 minutes.

Step 3: Create your Meta app

Instagram doesn't know your worker exists yet. Go to developers.facebook.com and create an app. Give it a name, put your email in, and when it asks for a use case, pick Manage messaging & content on Instagram.

Meta changes this screen, and its docs also describe picking Other first, so go with whatever yours shows.

Keep this app just for your DMs. If a key ever leaks, it can't reach anything else.

Step 4: Connect Instagram

  1. On that use case, press Customize and open API setup with Instagram login.
  2. Add your Instagram account, log in and press Generate token.
  3. Run the command Claude gives you in your terminal and paste the token in there, where it's stored as a secret. It's npx wrangler secret put IG_TOKEN.
  4. Do the same with the Instagram app secret from that page, using npx wrangler secret put IG_APP_SECRET. That's how your worker knows a message really came from Instagram.
  5. Give Claude your Instagram account ID from the same page, so it can finish the setup and deploy again.
  6. On your phone, open Instagram's message settings and turn on Allow Access to Messages under Connected Tools.

That token lets your worker send messages as you, so treat it like a password and never paste it into a chat.

Step 5: Connect the webhook

The webhook is how Instagram tells your worker someone commented. In the webhook section of that same page:

  1. Paste in the web address from Claude with /webhook on the end.
  2. Paste in the verify token Claude gave you.
  3. Press Verify and save. Instagram checks your worker's answer, and if it goes through, the two are talking.
  4. Subscribe to comments, messages and messaging_postbacks. Those are the comments themselves and the taps on your button.

Step 6: Publish the app

Meta only sends real comments to a published app. Your worker already has a privacy page and a data deletion page, so paste those two addresses from Claude's checklist into the app settings and press Publish.

I published mine with no App Review, and it's been answering people since day one. Meta's docs say comment webhooks need Advanced Access though, so yours might ask for it. If a comment never reaches the webhook, start there. The 15-minute check still picks that comment up.

Step 7: Test it from a second account

The worker skips your own comments on purpose, so you need a second account.

  1. Comment your keyword on one of your posts. You'll get an automatic public reply under the comment.
  2. Find the DM with the button. If that account doesn't follow you, it lands in message requests.
  3. Tap the button. If the account follows you, the link comes through. If it doesn't, you'll get asked to follow and tap again, until they follow.

After that

  • A new keyword is one sentence. Tell Claude Code the word, the message and the link, and it updates the file and deploys, ready for your next reel.
  • It checks your recent posts every 15 minutes. A comment Instagram never sent still gets its DM, as long as it's under 7 days old.
  • It refreshes your token every week. Those tokens die after 60 days, and one that's left to expire can't be refreshed.
  • From here, ask for the next thing you want. That's how mine ended up asking for an email 22 hours after someone was messaged, so it lands inside Instagram's 24-hour messaging window.

Credit

The follow check is based on OpenReply, an open-source repo under the MIT licence. I'm not affiliated with Manychat, Meta, Cloudflare or Anthropic.

Community now open

My AI Growth Room on Skool is free to join, and the Claude editing skill I use for my reels is in there.

Join free

Where to next

Three ways to get it done

Do it yourself

Free

Every guide points you at one AI tool that's worth your time, with the prompt or checklist to set it up yourself.

See all free guides

Done with you

Community now open

Go from watching AI videos to using AI in your own work every week, with live help and a group doing it alongside you.

Join free

Done for you

With ABAI

For owners of B2B businesses with a bottleneck that's costing them clients. We find it, then build the AI growth system that fixes it, in your own tools and on a fixed fee.

Book a strategy call