AI Auto-Responder for Instagram

Build a Flask webhook server that uses Claude to auto-reply to Instagram comments.

Introduction

30 Second Summary

Keeping up with Instagram comments takes real time. Every reply you write is time you could spend creating content instead.

In this project, you will build an AI-powered auto-responder for your Instagram account. Claude generates a thoughtful reply for every new comment, and your server posts it back in seconds.

What You'll Build

A Python Flask server that receives Instagram comment notifications via webhooks, sends the comment text to Claude for a contextual reply, and posts the response back through the Instagram Graph API.

By the end of this project, you'll have:

  • A Flask webhook server with an ngrok tunnel that receives real-time Instagram comment notifications.
  • A Claude AI integration that generates contextual, on-brand replies for each comment.
  • A working Instagram Graph API pipeline that automatically posts replies back to your posts.
  • In the Secret Mission, you'll extend the bot to auto-reply to incoming DMs using the Instagram Messaging API.

Are there any prerequisites?

You need an Instagram Creator or Business account to use the Instagram API. A personal account will not work, but you can switch to a Creator account for free in your Instagram settings. The AI replies cost approximately $3 for 2,000 comments using Claude Haiku.

Before We Start

Before We Start...

Install Python, Tools, and Create Accounts

Before you can build the auto-responder, you need a few tools in place. Python runs the server code. Flask gives you a lightweight web server. The Anthropic SDK connects you to Claude for generating replies.

You also need ngrok to expose your local server to the internet so Meta can deliver webhook notifications to it. In this step, you will install everything and create the accounts you need.

In this step, get ready to:
  • Install Python 3, create a project folder, and install the required packages.
  • Install ngrok and connect it to your free ngrok account.
  • Create an Anthropic account and set your API key as an environment variable.
Install Python and set up your project environment

The Anthropic Python SDK requires Python 3.9 or higher. Let's verify your installation first.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Check your Python version by running this command:
python3 --version

I see a version number

If you see 3.9 or higher (e.g. Python 3.13.13 or Python 3.14.4), you are good to go.

Command not found

Python is not installed yet. Follow the instructions for your operating system below.

macOS

  • Install Python by running this command:
brew install python3
Don't have Homebrew?

If brew is not recognized, install Homebrew first by visiting brew.sh and running the install command shown on the page.

  • Verify the installation by running:
python3 --version

You should see a version number of 3.9 or higher.

Windows

  • Go to python.org/downloads in your browser.
  • Click the Download Python button for the latest version.
  • Run the installer.
  • Check the Add python.exe to PATH checkbox at the bottom of the installer window.
  • Click Install Now.
  • Close and reopen Command Prompt, then verify the installation by running:
python --version

You should see a version number of 3.9 or higher.

Pro tip: Python command on Windows

On Windows, the command is usually python instead of python3. The rest of this project uses python3 in its examples. Substitute python wherever you see python3.

Now let's create a project folder and set up a virtual environment. A virtual environment keeps your project's packages isolated from the rest of your system.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir instagram-auto-responder
cd instagram-auto-responder
  • Create a virtual environment by running:
python3 -m venv venv
  • Activate the virtual environment by running:

macOS

source venv/bin/activate

Windows

venv\Scripts\activate

You should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.

Don't see (venv) in your prompt?

Make sure you ran the activate command from inside the instagram-auto-responder folder. If you are on Windows and see a permissions error, run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned in PowerShell first, then try activating again.

help me activate my Python virtual environment

Now install the three packages your project needs: Flask for the web server, the Anthropic SDK for Claude, and requests for making HTTP calls to the Instagram API.

  • Install the required packages by running:
pip install flask==3.1.3 anthropic==0.96.0 requests==2.33.1

What are these packages?

  • Flask (3.1.3) is a lightweight web framework. It handles incoming HTTP requests from Meta's webhook system.
  • anthropic (0.96.0) is the official Python SDK for calling the Claude API.
  • requests (2.33.1) is a popular HTTP library. You will use it to send replies back to Instagram.
  • Verify the packages installed by running:
pip list

You should see Flask, anthropic, and requests listed in the output.

Seeing an installation error?

Make sure your virtual environment is active (you should see (venv) in your prompt). If pip is not recognized, try pip3 instead.

help me install Flask, anthropic, and requests in my virtual environment

Install ngrok and create an account

Your Flask server will run on your local machine, but Meta's webhook system needs a public URL to send notifications to. Ngrok creates a secure tunnel from the internet to your local server.

  • Install ngrok by following the instructions for your operating system:

macOS

  • Install ngrok with Homebrew by running:
brew install ngrok

Windows

  • Open the Microsoft Store on your computer.
  • Search for ngrok and click Install.

Alternative Windows install methods

You can also install ngrok using WinGet, Scoop, or by downloading the installer directly from ngrok.com/downloads.

  • Verify that ngrok is installed by running:
ngrok help

You should see ngrok's help text printed in your terminal.

ngrok command not found?

If you installed via Homebrew, make sure Homebrew's bin directory is in your PATH. Try closing and reopening your terminal. On Windows, restart your terminal after the Microsoft Store installation finishes.

help me get ngrok working in my terminal

Now create a free ngrok account so you can authenticate your tunnel.

  • Go to ngrok.com in your browser.
  • Click Sign up and create a free account.
  • After signing in, go to Your Authtoken in the left sidebar of the ngrok dashboard.
  • Copy your authtoken.
  • Back in your terminal, connect ngrok to your account by running:
ngrok config add-authtoken "your-authtoken-here"

What does the authtoken do?

The authtoken links your local ngrok installation to your ngrok account. This lets ngrok create tunnels on your behalf and gives you access to the free tier's 20,000 requests per month.

You should see a confirmation message like Authtoken saved to configuration file.

Create an Anthropic account and set your API key

Claude is the AI that generates replies to Instagram comments. To use Claude's API, you need an Anthropic account and an API key.

  • Go to console.anthropic.com in your browser.
  • Click Sign up and create an account.
  • After signing in, click API keys in the left sidebar.
  • Click Create Key.
  • Give the key a name like instagram-bot and click Create Key.
  • Copy the key immediately. You will not be able to see it again after closing this dialog.

Save your API key somewhere safe

Paste your API key into a text file or password manager right away. Anthropic only shows the key once. If you lose it, you will need to create a new one.

The Anthropic SDK reads your API key from an environment variable called ANTHROPIC_API_KEY. Set it in your terminal so the SDK can find it automatically.

  • Set the environment variable by running:

macOS

export ANTHROPIC_API_KEY="your-api-key-here"

Windows

set ANTHROPIC_API_KEY=your-api-key-here

Why an environment variable?

Storing your API key in an environment variable keeps it out of your code. The Anthropic SDK automatically reads the ANTHROPIC_API_KEY variable when you create a client, so you never need to paste the key directly into your Python files.

  • Verify the variable is set by running:

macOS

echo $ANTHROPIC_API_KEY

Windows

echo %ANTHROPIC_API_KEY%

You should see your API key printed in the terminal output.

Seeing a blank output?

Make sure you ran the export (macOS) or set (Windows) command in the same terminal session. If you opened a new terminal window, you need to set the variable again.

help me set my Anthropic API key as an environment variable

Pro tip: persist your key

The export command only lasts for your current terminal session. To make it permanent, add the export ANTHROPIC_API_KEY="your-key" line to your ~/.zshrc (macOS) or set it in System Environment Variables (Windows).

Your development environment is ready. Next up, you will link your Instagram account to a Facebook Page and create the Meta app that powers your webhook connection.

Link Instagram to Facebook and Create a Meta App

Your development environment is set up and ready to go. Now it's time to connect your Instagram account to Meta's developer platform so your bot can receive notifications and post replies.

The Instagram Graph API is how your bot reads comments and posts replies. Before you can use it, Meta requires three things: a Facebook Page linked to your Instagram account, a registered Meta App, and an access token.

In this step, get ready to:
  • Link your Instagram Creator account to a Facebook Page.
  • Create a Meta Developer account and a new Meta App.
  • Generate an access token for the Instagram API.
Link your Instagram account to a Facebook Page

Meta's developer platform uses Facebook Pages as the bridge between your Instagram account and the API. Even if you never post on Facebook, the Page gives Meta a way to manage permissions and route webhook notifications for your Instagram account.

  • Open the Instagram app on your phone.
  • Tap your profile icon in the bottom right corner.
  • Tap the menu icon (three horizontal lines) in the top right corner.
  • Tap Settings and privacy.
  • Tap Account.
  • Tap Linked accounts.
  • Tap Facebook and log in with your Facebook credentials if prompted.
  • Select a Facebook Page to link to your Instagram account. If you don't have a Facebook Page yet, you'll see an option to create one.

Pro tip: can't find Linked accounts?

Instagram's settings menu varies slightly across app versions. If you don't see Linked accounts under Account, look for Connected accounts under Business or Creator tools instead. You can also try going to Accounts Center (under the Meta logo at the top of Settings) and connecting your accounts from there.

Why do you need a Facebook Page?

Meta uses Facebook Pages as the identity layer for its developer APIs. Your Page acts as the anchor that connects your Instagram account to Meta's infrastructure. This lets Meta route access tokens, permissions, and webhook subscriptions to the right account.

Create a Meta Developer account and app

The Meta Developer platform is where you configure your app's permissions and webhook settings. You need a Meta App to authenticate your API requests and receive Instagram notifications.

  • Open your browser and go to developers.facebook.com.
  • Click Get Started or Log In in the top right corner.
  • Log in with the same Facebook account you linked to Instagram in the previous substep.
  • Accept the Meta Developer Terms if prompted. This registers you as a Meta Developer.

Once your developer account is ready, create a new app.

  • From the Meta Developer Dashboard, click Create App in the upper right.
  • If you are asked to connect a business, you can skip this for now. Click Next to continue.
  • Select Other as the use case.
  • Click Next.
  • Select Business as the app type.
  • Click Next.
  • Enter a name for your app, such as Instagram Auto-Responder.
  • Add your contact email address.
  • Click Next (or Create App) to create your app.

You'll be redirected to your app's dashboard. Now add the Instagram product to your app.

  • On your app's dashboard, scroll down to the product list.
  • Find the Instagram product card.
  • Click Set up.

The API setup with Instagram login is automatically added to your app. This is the configuration you'll use to manage comments, send messages, and receive webhooks through the Instagram API.

What does the Meta App do?

Your Meta App acts as a container for all the permissions, tokens, and webhook configurations your bot needs. It's how Meta knows which Instagram account you're working with and what your server is allowed to do.

Generate an access token and enable Connected Tools

Your app needs an access token to authenticate API requests to Instagram. This token proves that your app has permission to read comments and post replies on your behalf.

  • In the left sidebar of your Meta App Dashboard, click Instagram.
  • Click API setup with Instagram login.
  • Under the Generate access tokens section, click Add Instagram Account. Your Instagram account must be public for this to work.
  • Log in to your Instagram account when prompted and grant the requested permissions.
  • Click Generate Token next to your Instagram account.
  • A token will appear. Copy this token and save it somewhere safe, like a notes app or a secure text file.

Keep your access token safe

Your access token gives full API access to your Instagram account. Never share it publicly or commit it to a Git repository. This token is valid for approximately 60 days before it needs to be regenerated.

Now set the access token as an environment variable in the same terminal session where you'll run your server. This keeps the token out of your code.

  • Set the token as an environment variable by running this command (replace the placeholder with your actual token):

macOS

export INSTAGRAM_ACCESS_TOKEN="your-access-token-here"

Windows

set INSTAGRAM_ACCESS_TOKEN="your-access-token-here"

What is an access token?

An access token is a credential that proves your app has permission to act on behalf of your Instagram account. Every API call your bot makes to read comments or post replies includes this token so Meta can verify the request is authorized.

One more setting is needed. Instagram requires you to explicitly allow apps to access your messages through the API.

  • Open the Instagram app on your phone.
  • Go to Settings and privacy.
  • Tap Messages and story replies.
  • Tap Message controls.
  • Tap Connected Tools.
  • Toggle on Allow Access to Messages.

Why enable Connected Tools?

This setting allows your Meta App to receive and respond to direct messages through the API. Even though the main project focuses on comment replies, enabling this now prepares your account for the Secret Mission later.

Having trouble generating a token?

Make sure your Instagram account is set to public. Private accounts cannot generate access tokens through the App Dashboard.

If your Instagram account doesn't appear when you click Add Instagram Account, verify that you're logging into the correct Instagram Creator account (not a personal account).

help me troubleshoot why my Instagram access token isn't generating in the Meta App Dashboard

Your Instagram account is linked, your Meta App is configured, and you have an access token ready. Next, you'll build the webhook server that catches Instagram's notifications in real time.

Build the Webhook Server

You have your Meta App configured and your Instagram access token saved. Now it's time to build the server that actually receives notifications when someone comments on your posts.

Your server needs two endpoints. One handles Meta's verification handshake, a one-time check to prove your server is real. The other receives live comment notifications as webhooks the moment they happen.

In this step, get ready to:
  • Create a Flask server with webhook verification and event-handling routes.
  • Parse Instagram comment notifications and filter out your own replies.
  • Test the server locally to confirm it starts without errors.
Create the Flask app file

Meta's webhook system sends HTTP requests to YOUR server. Before it starts delivering live events, it verifies your server is legitimate by sending a GET request with a special token you define.

Your server also needs to know your Instagram access token (from Step 2) and your Instagram user ID. Both get loaded from environment variables so you never hardcode credentials in your code.

  • Set your Instagram access token as an environment variable by running this command:

macOS

export INSTAGRAM_ACCESS_TOKEN="paste-your-instagram-access-token-here"

Windows

set INSTAGRAM_ACCESS_TOKEN=paste-your-instagram-access-token-here
  • Set your Instagram user ID as an environment variable by running this command:

macOS

export IG_USER_ID="[[IG_USER_ID=\"your Instagram user ID\"]]"

Windows

set IG_USER_ID=[[IG_USER_ID="your Instagram user ID"]]

Where do I find my Instagram user ID?

Go to your Meta App Dashboard. Navigate to Instagram > API setup with Instagram login. Your Instagram user ID is displayed next to the account you added in Step 2.

  • Create a new file called app.py in your project folder and add the following code:
import os
from flask import Flask, request, jsonify

# Create the Flask application
app = Flask(__name__)

# Token Meta will send to verify your webhook endpoint
VERIFY_TOKEN = "my_instagram_verify_token"
# Access token for the Instagram Graph API
INSTAGRAM_ACCESS_TOKEN = os.environ.get("INSTAGRAM_ACCESS_TOKEN", "your-access-token-here")
# Your Instagram user ID (used later for messaging)
IG_USER_ID = os.environ.get("IG_USER_ID", "your-ig-user-id-here")


# Webhook verification: Meta sends a GET request to confirm your server is real
@app.route("/webhook", methods=["GET"])
def verify_webhook():
    mode = request.args.get("hub.mode")
    token = request.args.get("hub.verify_token")
    challenge = request.args.get("hub.challenge")

    if mode == "subscribe" and token == VERIFY_TOKEN:
        print("Webhook verified successfully!")
        return challenge, 200
    else:
        print("Webhook verification failed.")
        return "Forbidden", 403


# Webhook handler: receives incoming events from Instagram
@app.route("/webhook", methods=["POST"])
def handle_webhook():
    data = request.get_json()
    print(f"Received webhook: {data}")

    return jsonify({"status": "ok"}), 200


if __name__ == "__main__":
    app.run(port=5000, debug=True)

What does this code do?

  • VERIFY_TOKEN is a secret string you choose. When Meta sends a verification request, your server checks that the incoming token matches this value. If it doesn't match, someone else is trying to connect to your webhook.
  • The GET route (verify_webhook) handles Meta's one-time handshake. Meta sends three query parameters: hub.mode, hub.verify_token, and hub.challenge. Your server returns the challenge value to prove it received the request.
  • The POST route (handle_webhook) is a placeholder for now. It logs whatever Meta sends and returns a 200 status so Meta knows the delivery succeeded.
  • Save app.py.

You won't see any changes yet. The next substep adds the comment parsing logic, and then you'll run the server.

Add comment-handling logic to the POST route

When someone comments on your Instagram post, Meta sends a POST request with a JSON payload containing nested data. The payload wraps the comment text, commenter's username, and comment ID inside several layers of objects and arrays.

What does the webhook payload look like?

Here is a simplified version of what Meta sends when someone leaves a comment:

{
  "object": "instagram",
  "entry": [{
    "changes": [{
      "field": "comments",
      "value": {
        "text": "Love this post!",
        "id": "COMMENT_ID",
        "from": {
          "username": "commenter_name"
        }
      }
    }]
  }]
}

Your code needs to drill through entry and changes to reach the actual comment data inside value.

  • In app.py, find the handle_webhook function. It currently looks like this:
@app.route("/webhook", methods=["POST"])
def handle_webhook():
    data = request.get_json()
    print(f"Received webhook: {data}")

    return jsonify({"status": "ok"}), 200
  • Replace the entire handle_webhook function (including the @app.route decorator) with this expanded version:
# Webhook handler: receives and processes incoming comment events
@app.route("/webhook", methods=["POST"])
def handle_webhook():
    data = request.get_json()
    print(f"Received webhook: {data}")

    # Ignore events that aren't from Instagram
    if data.get("object") != "instagram":
        return jsonify({"status": "ignored"}), 200

    for entry in data.get("entry", []):
        for change in entry.get("changes", []):
            # Only process comment events
            if change.get("field") != "comments":
                continue

            # Extract the comment details from the payload
            value = change.get("value", {})
            comment_text = value.get("text", "")
            comment_id = value.get("id", "")
            from_user = value.get("from", {})
            username = from_user.get("username", "someone")

            # Skip if essential data is missing
            if not comment_text or not comment_id:
                continue

            # Skip your own comments to avoid infinite reply loops
            self_ig_scoped_id = from_user.get("self_ig_scoped_id")
            if self_ig_scoped_id:
                print(f"Skipping own comment from {username}")
                continue

            print(f"New comment from @{username}: {comment_text}")

    return jsonify({"status": "ok"}), 200

What does this code do?

  • The first check (data.get("object") != "instagram") filters out events that don't come from Instagram. Meta could send notifications for other products too.
  • The nested loop walks through entry and changes to reach each individual event. It only processes events where field is "comments".
  • The echo detection checks for self_ig_scoped_id in the from field. This field is present when the comment comes from your own account. Without this filter, your bot's replies would trigger new webhook events, creating an infinite loop.

Why is echo filtering so important?

When your bot posts a reply, that reply is itself a comment on the post. Meta sends a new webhook notification for every comment, including your bot's replies. Without echo filtering, your bot would see its own reply, generate another reply to that, and keep going forever.

  • Save app.py.

You won't see a change yet. The next substep runs the server to confirm everything works.

Seeing a red underline or import error?

  • Make sure your virtual environment is activated. You should see your environment name in parentheses at the start of your terminal prompt.
  • Confirm that Flask is installed by running pip list and looking for flask in the output.
  • Check that your file is named exactly app.py (not App.py or flask.py). Naming your file flask.py conflicts with the Flask library itself.

Still stuck? Ask for help here.

Awesome, I've got everything!

Great. Double check you've saved app.py before moving on.

I'd like to double check the full code

import os
from flask import Flask, request, jsonify

app = Flask(__name__)

VERIFY_TOKEN = "my_instagram_verify_token"
INSTAGRAM_ACCESS_TOKEN = os.environ.get("INSTAGRAM_ACCESS_TOKEN", "your-access-token-here")
IG_USER_ID = os.environ.get("IG_USER_ID", "your-ig-user-id-here")


@app.route("/webhook", methods=["GET"])
def verify_webhook():
    mode = request.args.get("hub.mode")
    token = request.args.get("hub.verify_token")
    challenge = request.args.get("hub.challenge")

    if mode == "subscribe" and token == VERIFY_TOKEN:
        print("Webhook verified successfully!")
        return challenge, 200
    else:
        print("Webhook verification failed.")
        return "Forbidden", 403


@app.route("/webhook", methods=["POST"])
def handle_webhook():
    data = request.get_json()
    print(f"Received webhook: {data}")

    if data.get("object") != "instagram":
        return jsonify({"status": "ignored"}), 200

    for entry in data.get("entry", []):
        for change in entry.get("changes", []):
            if change.get("field") != "comments":
                continue

            value = change.get("value", {})
            comment_text = value.get("text", "")
            comment_id = value.get("id", "")
            from_user = value.get("from", {})
            username = from_user.get("username", "someone")

            if not comment_text or not comment_id:
                continue

            self_ig_scoped_id = from_user.get("self_ig_scoped_id")
            if self_ig_scoped_id:
                print(f"Skipping own comment from {username}")
                continue

            print(f"New comment from @{username}: {comment_text}")

    return jsonify({"status": "ok"}), 200


if __name__ == "__main__":
    app.run(port=5000, debug=True)
Test the server locally

Before connecting to Meta, confirm that your Flask server starts without any syntax or import errors.

  • Start your server by running this command:
python app.py

You should see output similar to this in your terminal:

 * Serving Flask app 'app'
 * Debug mode: on
 * Running on http://127.0.0.1:5000

The server is now running on port 5000. Leave it running for now. You can stop it later with Ctrl+C.

Server won't start?

  • If you see Address already in use, another process is using port 5000. Stop it first, or change the port in your code to 5001.
  • If you see ModuleNotFoundError: No module named 'flask', your virtual environment is not activated. Activate it and try again.
  • If you see a SyntaxError, check for missing colons, mismatched parentheses, or unclosed strings in app.py. Use the double-check tab above to compare your code.

Need more help troubleshooting? Ask here.

Your webhook server is running and ready to receive Instagram comment notifications. Next up, you'll connect Claude to generate intelligent replies to those comments.

Connect Claude to Generate Replies

Your webhook server can receive Instagram comment notifications and extract the important details. Right now, though, it just parses the data and moves on.

In this step, you'll bring your bot to life by connecting it to Claude. You'll build a function that sends each incoming comment to Claude Haiku 4.5 and gets back a short, friendly reply.

In this step, get ready to:
  • Create a system prompt and a function that generates replies using Claude.
  • Wire the Claude function into your webhook handler with logging.
Create the reply generation function

Claude needs instructions to know how to reply. A system prompt sets the tone and constraints for every reply Claude generates.

  • In app.py, find the line that says client = Anthropic(). Copy the following code below it:
SYSTEM_PROMPT = (
    "You are a friendly social media assistant replying to Instagram comments. "
    "Keep replies short (1-2 sentences), positive, and conversational. "
    "Do not use hashtags. Do not be overly formal."
)


def generate_reply(comment_text, username):
    # Send the comment to Claude and return the generated reply
    message = client.messages.create(
        model="claude-haiku-4-5-20241022",
        max_tokens=150,
        system=SYSTEM_PROMPT,
        messages=[
            {
                "role": "user",
                "content": f"Someone named @{username} commented on my Instagram post: \"{comment_text}\"\n\nWrite a short, friendly reply.",
            }
        ],
    )
    return message.content[0].text

What does this code do?

  • SYSTEM_PROMPT tells Claude to act as a social media assistant. It keeps replies short, positive, and free of hashtags.
  • generate_reply() takes a comment's text and the commenter's username, packages them into a prompt, and sends them to Claude Haiku 4.5.
  • max_tokens=150 caps the reply length so Claude doesn't write an essay under someone's post.
  • message.content[0].text extracts just the text string from Claude's response object.

Why Claude Haiku 4.5?

Haiku 4.5 is the fastest and most cost-effective Claude model. At $1 per million input tokens and $5 per million output tokens, 2,000 Instagram replies cost roughly $3. For short social media replies, Haiku's quality is more than sufficient.

  • Save app.py.

You won't see a change yet. The next substep wires this function into your webhook handler so it actually gets called.

Seeing a syntax error when you save?

  • Make sure the SYSTEM_PROMPT and generate_reply code is placed after client = Anthropic() but before the @app.route lines. Order matters in Python.
  • Check that the parentheses in SYSTEM_PROMPT = ( are properly closed with ) on its own line.
  • Confirm the f-string in the content field uses escaped quotes \" around {comment_text}. Mismatched quotes break the string.

Help me debug a syntax error in my generate_reply function

Wire Claude into the webhook handler

The generate_reply function exists, but nothing calls it yet. You need to connect it to the webhook handler so that every incoming comment triggers a Claude reply.

  • In the handle_webhook function in app.py, scroll down to the echo-detection block.
  • Find these lines:
            self_ig_scoped_id = from_user.get("self_ig_scoped_id")
            if self_ig_scoped_id:
                print(f"Skipping own comment from {username}")
                continue
  • Copy the following code directly below that continue statement, still inside the for change loop:
            # Log the incoming comment
            print(f"New comment from @{username}: {comment_text}")

            # Generate a reply using Claude
            reply_text = generate_reply(comment_text, username)
            print(f"Claude's reply: {reply_text}")

What does this code do?

  • The first print logs the commenter's username and their comment text to your terminal. This is your real-time feed of incoming activity.
  • generate_reply() sends the comment off to Claude and stores the response in reply_text.
  • The second print logs Claude's generated reply so you can see exactly what it wrote before it gets posted.
  • Save app.py.
  • Restart your Flask server by pressing Ctrl+C in the terminal to stop it, then run it again:
python app.py

You should see Flask start up on port 5000 without errors. The Claude integration won't fire until a real webhook arrives, which you'll set up in Step 6.

Server crashing on startup?

  • Check that your ANTHROPIC_API_KEY environment variable is set in the same terminal session where you run the server. The Anthropic client reads this automatically on startup.
  • Make sure your virtual environment is activated. You should see your environment name in parentheses at the start of your terminal prompt.
  • Verify the indentation of the new code matches the surrounding lines. Python is strict about indentation levels.

Help me fix my Flask server that crashes after adding Claude integration

Awesome, I've got everything!

Double check you've saved app.py and that your server restarts without errors.

I'd like to double check the full code

Here is what your complete app.py should look like at this point.

import os
import requests
from flask import Flask, request, jsonify
from anthropic import Anthropic

app = Flask(__name__)

VERIFY_TOKEN = "my_instagram_verify_token"
INSTAGRAM_ACCESS_TOKEN = os.environ.get("INSTAGRAM_ACCESS_TOKEN", "your-access-token-here")
IG_USER_ID = os.environ.get("IG_USER_ID", "your-ig-user-id-here")

client = Anthropic()

SYSTEM_PROMPT = (
    "You are a friendly social media assistant replying to Instagram comments. "
    "Keep replies short (1-2 sentences), positive, and conversational. "
    "Do not use hashtags. Do not be overly formal."
)


def generate_reply(comment_text, username):
    message = client.messages.create(
        model="claude-haiku-4-5-20241022",
        max_tokens=150,
        system=SYSTEM_PROMPT,
        messages=[
            {
                "role": "user",
                "content": f"Someone named @{username} commented on my Instagram post: \"{comment_text}\"\n\nWrite a short, friendly reply.",
            }
        ],
    )
    return message.content[0].text


@app.route("/webhook", methods=["GET"])
def verify_webhook():
    mode = request.args.get("hub.mode")
    token = request.args.get("hub.verify_token")
    challenge = request.args.get("hub.challenge")

    if mode == "subscribe" and token == VERIFY_TOKEN:
        print("Webhook verified successfully!")
        return challenge, 200
    else:
        print("Webhook verification failed.")
        return "Forbidden", 403


@app.route("/webhook", methods=["POST"])
def handle_webhook():
    data = request.get_json()
    print(f"Received webhook: {data}")

    if data.get("object") != "instagram":
        return jsonify({"status": "ignored"}), 200

    for entry in data.get("entry", []):
        for change in entry.get("changes", []):
            if change.get("field") != "comments":
                continue

            value = change.get("value", {})
            comment_text = value.get("text", "")
            comment_id = value.get("id", "")
            from_user = value.get("from", {})
            username = from_user.get("username", "someone")

            if not comment_text or not comment_id:
                continue

            self_ig_scoped_id = from_user.get("self_ig_scoped_id")
            if self_ig_scoped_id:
                print(f"Skipping own comment from {username}")
                continue

            print(f"New comment from @{username}: {comment_text}")

            reply_text = generate_reply(comment_text, username)
            print(f"Claude's reply: {reply_text}")

    return jsonify({"status": "ok"}), 200


if __name__ == "__main__":
    app.run(port=5000, debug=True)

Claude is ready to generate replies whenever a comment comes in. Next up, you'll build the function that actually posts those replies back to Instagram so commenters see them.

Post Replies Back to Instagram

Claude is generating replies to Instagram comments. Right now, those replies only appear as log messages in your terminal.

In this step, you'll close the loop by posting Claude's replies back to Instagram as real comments. You'll use the Instagram Graph API to send each reply directly to the comment thread where it belongs.

In this step, get ready to:
  • Create a function that posts replies to Instagram using the Graph API.
  • Wire the reply function into your webhook handler for a complete auto-reply flow.
Create the reply posting function

The Graph API exposes a reply endpoint for every comment. Your function sends a POST request to /{ig-comment-id}/replies with the reply text and your access token.

  • In app.py, find your generate_reply function. Add the following new function directly below it:
def post_reply(comment_id, reply_text):
    # Build the Graph API URL targeting the specific comment
    url = f"https://graph.instagram.com/v25.0/{comment_id}/replies"
    params = {
        "message": reply_text,
        "access_token": INSTAGRAM_ACCESS_TOKEN,
    }
    # Send the reply to Instagram
    response = requests.post(url, params=params)
    if response.status_code == 200:
        print(f"Reply posted successfully: {response.json()}")
    else:
        print(f"Error posting reply: {response.status_code} {response.text}")
    return response

What does this code do?

  • The URL targets the /replies edge of the specific comment using its ID. This tells Instagram to post your text as a reply to that comment, not as a new top-level comment.
  • params sends the reply text and your access token as query parameters, which is how the Graph API authenticates the request.
  • The if/else block checks whether Instagram accepted the reply. If the status code is not 200, it logs the error details so you can debug.

What can this endpoint reply to?

The /replies endpoint only works on top-level comments. If someone replies to an existing reply, your bot's response gets added to the top-level comment thread instead of nesting further. It also cannot reply to hidden comments or live video comments.

  • Save app.py.

You won't see a change yet. The next substep wires this function into your webhook handler.

Wire the reply function into the webhook handler

Your webhook handler already receives comments and generates replies with Claude. The last piece is calling post_reply to send Claude's response back to Instagram.

  • In app.py, scroll to the handle_webhook function. Find these two lines inside the comment-handling loop:
            reply_text = generate_reply(comment_text, username)
            print(f"Claude's reply: {reply_text}")
  • Add this line directly below print(f"Claude's reply: {reply_text}"):
            post_reply(comment_id, reply_text)

What's happening here?

This single line completes the entire auto-reply pipeline. When a comment comes in, your handler now extracts the comment, sends it to Claude, and posts Claude's reply back to Instagram. All three steps happen automatically every time a webhook fires.

  • Save app.py.
  • Restart your Flask server by pressing Ctrl+C in the terminal to stop it, then running:
python app.py

You should see * Running on http://127.0.0.1:5000 in the terminal with no errors.

Seeing a NameError or IndentationError?

  • If you see NameError: name 'post_reply' is not defined, make sure your post_reply function is defined above the @app.route decorators. Python needs to see the function before it is called.
  • If you see an IndentationError, double-check that the post_reply(comment_id, reply_text) line is indented to the same level as the print line above it. Both should be inside the comment-handling loop.
  • My Flask server won't start after adding the reply function.

Awesome, I've got everything!

Double-check you've saved app.py before moving on.

I'd like to double check the full code

Here is the complete app.py file after this step. Compare it against yours to catch any differences.

import os
import requests
from flask import Flask, request, jsonify
from anthropic import Anthropic

app = Flask(__name__)

VERIFY_TOKEN = "my_instagram_verify_token"
INSTAGRAM_ACCESS_TOKEN = os.environ.get("INSTAGRAM_ACCESS_TOKEN", "your-access-token-here")
IG_USER_ID = os.environ.get("IG_USER_ID", "your-ig-user-id-here")

client = Anthropic()

SYSTEM_PROMPT = (
    "You are a friendly social media assistant replying to Instagram comments. "
    "Keep replies short (1-2 sentences), positive, and conversational. "
    "Do not use hashtags. Do not be overly formal."
)


def generate_reply(comment_text, username):
    message = client.messages.create(
        model="claude-haiku-4-5-20241022",
        max_tokens=150,
        system=SYSTEM_PROMPT,
        messages=[
            {
                "role": "user",
                "content": f"Someone named @{username} commented on my Instagram post: \"{comment_text}\"\n\nWrite a short, friendly reply.",
            }
        ],
    )
    return message.content[0].text


def post_reply(comment_id, reply_text):
    url = f"https://graph.instagram.com/v25.0/{comment_id}/replies"
    params = {
        "message": reply_text,
        "access_token": INSTAGRAM_ACCESS_TOKEN,
    }
    response = requests.post(url, params=params)
    if response.status_code == 200:
        print(f"Reply posted successfully: {response.json()}")
    else:
        print(f"Error posting reply: {response.status_code} {response.text}")
    return response


@app.route("/webhook", methods=["GET"])
def verify_webhook():
    mode = request.args.get("hub.mode")
    token = request.args.get("hub.verify_token")
    challenge = request.args.get("hub.challenge")

    if mode == "subscribe" and token == VERIFY_TOKEN:
        print("Webhook verified successfully!")
        return challenge, 200
    else:
        print("Webhook verification failed.")
        return "Forbidden", 403


@app.route("/webhook", methods=["POST"])
def handle_webhook():
    data = request.get_json()
    print(f"Received webhook: {data}")

    if data.get("object") != "instagram":
        return jsonify({"status": "ignored"}), 200

    for entry in data.get("entry", []):
        for change in entry.get("changes", []):
            if change.get("field") != "comments":
                continue

            value = change.get("value", {})
            comment_text = value.get("text", "")
            comment_id = value.get("id", "")
            from_user = value.get("from", {})
            username = from_user.get("username", "someone")

            if not comment_text or not comment_id:
                continue

            self_ig_scoped_id = from_user.get("self_ig_scoped_id")
            if self_ig_scoped_id:
                print(f"Skipping own comment from {username}")
                continue

            print(f"New comment from @{username}: {comment_text}")

            reply_text = generate_reply(comment_text, username)
            print(f"Claude's reply: {reply_text}")

            post_reply(comment_id, reply_text)

    return jsonify({"status": "ok"}), 200


if __name__ == "__main__":
    app.run(port=5000, debug=True)

Your auto-reply pipeline is now complete in code. Next up, you'll expose your server to the internet with ngrok and connect it to Meta's webhooks to test the full flow with a real Instagram comment.

Connect Webhooks and Test End-to-End

Your auto-responder code is complete. You have a Flask server that receives webhooks, sends comments to Claude, and posts replies back to Instagram.

There is one problem. Your server is running on your computer, and Meta has no way to reach it over the internet. In this step, you will expose your local server with ngrok and register its public URL in the Meta App Dashboard so webhook notifications flow to your machine in real time.

In this step, get ready to:
  • Start your Flask server and create a public ngrok tunnel.
  • Register your webhook URL in the Meta App Dashboard.
  • Test the full flow from Instagram comment to AI-generated reply.
Start your server and create an ngrok tunnel

Meta needs a publicly accessible HTTPS URL to send webhook events to. ngrok creates a secure tunnel from the internet to your local machine, giving you a public URL that forwards traffic to your Flask server on port 5000.

Before you start, make sure your environment variables are set in the terminal session you will use. You need ANTHROPIC_API_KEY and INSTAGRAM_ACCESS_TOKEN from the earlier steps.

  • In your terminal, navigate to your project folder and start your Flask server by running:
python app.py

What should I see?

You should see output like * Running on http://127.0.0.1:5000. This means your server is running and waiting for incoming requests. Leave this terminal window open.

Seeing an error when starting the server?

  • If you see ModuleNotFoundError, make sure your virtual environment is activated and you ran pip install -r requirements.txt.
  • If port 5000 is already in use, close any other server running on that port and try again.
  • help me debug my Flask server startup error
  • Open a second terminal window. Press Cmd+Space (macOS) or the Windows key (Windows), type Terminal, and press Enter.
  • Create a public tunnel to your Flask server by running:
ngrok http 5000

What should I see?

ngrok displays a status panel in your terminal. Look for the Forwarding line. It shows an HTTPS URL like https://xxxx-xxxx.ngrok-free.app pointing to your local server.

  • Copy the full HTTPS forwarding URL from the ngrok output. You will need this in the next substep.

Pro tip: ngrok URLs change on restart

On the free tier, ngrok assigns a new URL every time you restart the tunnel. If you stop and restart ngrok, you will need to update the callback URL in the Meta Dashboard again.

ngrok not working?

  • If you see command not found: ngrok, ngrok is not installed. Go back to Step 1 and follow the ngrok installation instructions.
  • If ngrok shows an authentication error, make sure you added your authtoken by running ngrok config add-authtoken with your token from the ngrok dashboard.
  • help me fix my ngrok tunnel
Register your webhook in the Meta App Dashboard

Your server is running and publicly accessible through ngrok. Now you need to tell Meta where to send Instagram webhook events by registering your callback URL in the App Dashboard.

  • Open developers.facebook.com in your browser and navigate to your app.
  • In the left sidebar, click Instagram to expand the menu.
  • Click Webhooks under the Instagram section.
  • In the Callback URL field, paste your ngrok forwarding URL with /webhook appended to the end. For example: https://xxxx-xxxx.ngrok-free.app/webhook.
  • In the Verify token field, enter my_instagram_verify_token. This must match the VERIFY_TOKEN value in your app.py file.
  • Click Verify and Save.

What happens during verification?

When you click Verify and Save, Meta sends a GET request to your callback URL with a hub.verify_token and a hub.challenge value.

Your Flask server checks that the token matches, then returns the challenge value. This proves to Meta that your server is real and ready to receive events.

Check your Flask terminal. You should see Webhook verified successfully! printed in the logs. If the Meta Dashboard shows a green confirmation, your webhook is registered.

  • On the same webhooks page, verify that the comments field is subscribed. This tells Meta to send you a notification whenever someone comments on one of your posts.

What fields are subscribed by default?

Meta subscribes your app to several webhook fields by default, including comments, messages, and message_reactions. You only need comments for this project, but having the others subscribed does no harm.

One more critical setting. Your Meta app needs to be in Live mode for webhooks to work with accounts that do not have a role on your app.

  • In the top bar of your App Dashboard, find the App Mode toggle.
  • Switch it from Development to Live.

Live mode is required for external testing

In Development mode, Meta only delivers webhooks for users who have a role on your app (like you). If you want someone else to trigger your auto-responder by commenting on your post, your app must be in Live mode.

Verification failed?

  • Make sure your Flask server is still running in the first terminal. If it crashed, restart it with python app.py.
  • Make sure ngrok is still running in the second terminal. If you restarted ngrok, the URL changed and you need to paste the new one.
  • Double check that you appended /webhook to the end of your ngrok URL. Without it, Meta hits the wrong route.
  • Confirm the verify token is exactly my_instagram_verify_token with no extra spaces.
  • help me troubleshoot my Meta webhook verification failure
Test the end-to-end flow

Everything is connected. Your Flask server is running, ngrok is tunneling traffic to it, and Meta knows where to send webhook events. Time to see the full auto-responder in action.

  • Open Instagram on your phone or in a browser.
  • Find one of your posts. If you do not have any posts, publish a quick photo or image.
  • Have someone else comment on your post. Use a friend's account, a second Instagram account, or ask someone to leave a test comment.

Why not comment from your own account?

Your code filters out comments from your own account using the self_ig_scoped_id field. This prevents infinite reply loops where the bot replies to its own replies. A comment from your own account gets skipped on purpose.

  • Switch to your Flask terminal and watch the logs.

Within a few seconds of the comment being posted, you should see a sequence of log messages in your terminal.

What should I see in the terminal?

Your terminal should print three key messages in order:

  • New comment from @username: followed by the comment text. This confirms Meta delivered the webhook.
  • Claude's reply: followed by the AI-generated response. This confirms Claude received the comment and generated a reply.
  • Reply posted successfully: followed by a JSON response from the Instagram Graph API. This confirms the reply was posted back to Instagram.
  • Go back to your Instagram post and refresh the comments. You should see your bot's reply appear as a response to the original comment.

That is your AI auto-responder working end to end. A real comment triggered a webhook, Claude crafted a response, and the Instagram Graph API posted it back as a reply.

No webhook showing up in your terminal?

  • Check that your app is in Live mode in the Meta App Dashboard. Development mode only delivers webhooks for accounts with a role on the app.
  • Confirm ngrok is still running. If you accidentally closed the terminal, the tunnel is gone and Meta cannot reach your server.
  • Verify that the comments webhook field is subscribed in your App Dashboard under the Instagram webhooks settings.
  • Make sure the comment came from a different account than the one linked to your app. Your code skips your own comments to prevent reply loops.

Webhook received but reply not posting?

  • If you see Error posting reply: in your terminal, check that your INSTAGRAM_ACCESS_TOKEN environment variable is set correctly. Tokens from the App Dashboard last about 60 days.
  • Make sure you are replying to a top-level comment. The Instagram Graph API cannot reply to replies on a reply or to hidden comments.
  • help me debug why my Instagram reply is not posting

Secret mission

Auto-Reply to Incoming DMs

Your bot handles public comments like a pro. But what about private messages? Extend your auto-responder to also handle Instagram DMs using the Messaging API and a conversation-friendly system prompt.

Before you go

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs locally with ngrok, so there are no ongoing cloud hosting costs. However, the Anthropic API charges per use (~$3 per 2,000 replies with Claude Haiku 4.5), so keep that in mind if your server stays active.

Resources you used:

  • Python virtual environment and project folder with app.py and requirements.txt.
  • Flask server running on port 5000.
  • ngrok tunnel (free tier account and active tunnel).
  • Meta Developer App with Instagram webhooks configured.
  • Instagram-to-Facebook Page link.
  • Anthropic API key (set as environment variable).
  • Instagram access token (set as environment variable).

Keep everything running

No action needed. Choose this if you are still actively building or testing your auto-responder.

  • Keep your Flask server and ngrok tunnel running in their terminal windows.
  • Your Meta App and webhook subscription will stay active.
  • Be aware that every comment on your posts will trigger a Claude API call, which costs money. Monitor your Anthropic usage at console.anthropic.com.

Pause - I'll come back to this later

Shut down running processes to stop API usage, but keep your files and accounts ready to pick up again later.

  • Stop your Flask server by pressing Ctrl+C in its terminal window.
  • Stop ngrok by pressing Ctrl+C in its terminal window. Without the tunnel, Meta cannot deliver webhooks to your server.
  • Keep your project folder, virtual environment, and Meta App as they are.
  • When you come back, reactivate your virtual environment, restart Flask, restart ngrok, and update the webhook callback URL in your Meta App Dashboard with the new ngrok URL.
  • Your Instagram access token lasts approximately 60 days. If it has expired, generate a new one in the App Dashboard.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Stop your Flask server and ngrok tunnel by pressing Ctrl+C in both terminal windows.
  • Delete your project folder and virtual environment from your computer.
  • Go to developers.facebook.com, open your app, go to App Settings > Basic, scroll down, and click Delete App.
  • Delete your Anthropic API key at console.anthropic.com under API Keys.
  • Unset your environment variables by closing the terminal sessions where you exported them, or remove them from your shell profile if you added them there.
  • Optionally unlink your Instagram account from your Facebook Page if you no longer need the connection. Go to Instagram Settings > Account > Linked Accounts to disconnect.

Mission accomplished

That's a Wrap!

You just built an AI-powered Instagram auto-responder from scratch. Comments come in, Claude crafts a reply, and it shows up on your post within seconds. That is a real automation pipeline connecting webhooks, AI, and a social media API into one working system.

Here's what you accomplished:

  • Built a Flask webhook server that handles Meta's verification handshake and processes real-time Instagram comment notifications.
  • Integrated the Claude API to generate contextual, on-brand replies to Instagram comments automatically.
  • Connected the Instagram Graph API and ngrok to create a full end-to-end pipeline from incoming webhook to posted reply.
  • Extended the bot to handle Instagram DMs using the Messaging API, complete with echo filtering and automation disclosure.

Ready to quiz yourself?