```html

Integrating Instagram Graph API with AWS Lambda: Connecting @sailjada's Guest Photo Feed

What Was Done

We enabled Instagram media integration for the guest photo page system at shipcaptaincrew.queenofsandwick.com/g/{event_id} by configuring the Instagram Graph API within the Facebook Developer app and establishing a token exchange flow to populate the Lambda function with authentication credentials. This allows the existing Lambda function to fetch and display @sailjada's Instagram posts alongside user-uploaded charter photos from matching dates and times.

The Problem

The shipcaptaincrew Lambda function (deployed in us-east-1, AWS account 782785212866) contained dormant Instagram integration code that returned an empty array whenever the environment variables IG_USER_ID and IG_ACCESS_TOKEN were missing. The function logic was sound—it filters Instagram media by timestamp to match the event date—but lacked the credentials to authenticate against the Instagram Graph API.

Technical Architecture

Lambda Function Structure

The shipcaptaincrew function accepts event parameters including event_id (formatted as YYYY-MM-DD), queries the guest photo database, and conditionally fetches Instagram media when credentials are present. The Instagram integration pattern uses:

  • Environment variables: IG_USER_ID (the Instagram Business Account ID) and IG_ACCESS_TOKEN (a long-lived Graph API token with 60-day expiration)
  • Boto3 Lambda client: Updates function configuration via update_function_configuration() to inject credentials securely
  • Graph API endpoints: The function will call https://graph.instagram.com/{IG_USER_ID}/media with the access token to retrieve recent posts

Why This Approach?

Rather than hardcoding credentials in the function code or storing them in AWS Secrets Manager (which adds API call latency), we use Lambda environment variables because:

  • They're encrypted at rest using the default AWS KMS key for the account
  • They're injected at function invocation time with minimal overhead
  • They can be rotated without redeploying function code
  • The 60-day token expiration naturally forces a monthly refresh cadence, reducing token compromise risk

Facebook Developer App Configuration

Step 1: Add the Instagram Graph API Product

The first critical mistake to avoid: the existing Messaging API product (added for DM use cases) does not grant the instagram_basic scope required to read media. You must add Instagram Graph API separately:

  1. Navigate to developers.facebook.com/apps
  2. Select the sailjada-social app
  3. In the left sidebar, scroll to the bottom and click Add Product
  4. Search for "Instagram" and select Instagram Graph API (not Basic Display, not Messaging)
  5. Complete the setup flow and the product will appear in your sidebar

Step 2: Link the @sailjada Account

The @sailjada Instagram account must be a Business or Creator account and linked to a Facebook Page. Within Instagram Graph API → API setup, click "Add Instagram account" and authenticate as @sailjada. This establishes the relationship between the Facebook Page and the Instagram Business Account.

Step 3: Generate a Short-Lived Access Token

Using the Graph API Explorer at developers.facebook.com/tools/explorer:

  • Select the sailjada-social app from the dropdown
  • Click "Generate Access Token"
  • Select the Facebook Page linked to @sailjada
  • Request scopes: instagram_basic and pages_show_list
  • Copy the generated token (valid for ~2 hours)

Credential Extraction and Token Exchange

Retrieving IG_USER_ID

Make two sequential Graph API calls with the short-lived token:

# First: Get the Facebook Page ID
curl -s "https://graph.facebook.com/v18.0/me/accounts?access_token=TOKEN" | jq '.data[] | select(.name=="[Your Page Name]") | .id'

# Second: Get the Instagram Business Account ID from the Page
curl -s "https://graph.facebook.com/v18.0/PAGE_ID?fields=instagram_business_account&access_token=TOKEN" | jq '.instagram_business_account.id'

The second response's id field is your IG_USER_ID. Store this value separately—it doesn't change.

Exchanging for a Long-Lived Token

Short-lived tokens expire quickly. Exchange it for a long-lived token (60-day validity) using your app credentials:

curl -s "https://graph.instagram.com/access_token" \
  -d "grant_type=ig_refresh_token" \
  -d "access_token=SHORT_LIVED_TOKEN" | jq '.access_token'

This returns IG_ACCESS_TOKEN, valid for approximately 60 days.

Lambda Deployment

Updating Environment Variables

With both credentials obtained, inject them into the Lambda function:

aws lambda update-function-configuration \
  --region us-east-1 \
  --function-name shipcaptaincrew \
  --environment Variables="{IG_USER_ID=VALUE,IG_ACCESS_TOKEN=VALUE}"

The function is now configured to fetch Instagram media on invocation.

Key Design Decisions

  • Environment variables over Secrets Manager: Reduces API latency and simplifies rotation logic for tokens with known expiration windows
  • Long-lived tokens (60-day): Balances security (frequent rotation) against operational overhead (no complex refresh logic needed in the Lambda itself)
  • Monthly manual refresh: A scheduled task (could use EventBridge) to execute the token exchange call 55 days after each generation prevents unexpected expirations
  • Timestamp-based filtering in Lambda: The function filters Instagram posts by event date rather than relying on Instagram's built-in pagination, ensuring exact temporal alignment with guest photos

Verification and Testing

After deployment, test the integration by navigating to the guest photo page URL with a known event date:

https://shipcaptaincrew.queenofsandwick.com/g/2026-04-29

The page should now display both guest-uploaded photos and @sailjada's Instagram posts from that date in the same gallery view. Check CloudWatch Logs for the shipcaptaincrew function (log group: /aws/lambda/shipcaptaincrew) to verify the Graph API calls succeed and media is returned.

What's Next

Future enhancements should