Integrating Instagram Graph API with AWS Lambda for Dynamic Guest Photo Galleries

We recently enhanced the guest photo page system at shipcaptaincrew.queenofsandience.com/g/{event_id} to surface Instagram posts from the @sailjada account alongside user-uploaded charter photos. This post walks through the architecture decisions, setup process, and implementation details for integrating Instagram's Graph API with a dormant Lambda function to create a unified photo experience.

What Was Done

The shipcaptaincrew Lambda function (deployed in us-east-1, account 782785212866) contained scaffolding for Instagram integration that was inactive due to missing environment variables. We:

  • Configured the Instagram Graph API product in the Facebook Developer app dashboard
  • Generated short-lived and long-lived access tokens with proper scopes
  • Retrieved the Instagram Business Account ID for @sailjada
  • Updated Lambda environment variables to activate the dormant integration
  • Established a token refresh strategy for the 60-day expiration window

Technical Details: Instagram Graph API Setup

Why Instagram Graph API? The guest photo page needed to fetch media metadata (image URLs, captions, timestamps) from @sailjada's account to filter by event date/time windows. Instagram's Graph API provides direct access to business account media through OAuth tokens, allowing server-side queries without user interaction.

Product Configuration Issue: The initial app configuration included Instagram Messaging (for DM handling), which grants different OAuth scopes than media reading. Instagram Graph API requires the instagram_basic and pages_show_list scopes—these are only granted when Instagram Graph API is explicitly added as a product. The messaging product alone won't work for media access.

Step 1: Add Instagram Graph API Product

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

Step 2: Connect the Business Account

Inside the Instagram Graph API product dashboard, locate API Setup. Click Add Instagram Account and authenticate as @sailjada. This registers the account as a business/creator account within your app's permissions model.

Step 3: Generate Short-Lived Access Token

Using the Facebook Graph API Explorer:

  • Visit developers.facebook.com/tools/explorer
  • Select the sailjada-social app from the dropdown
  • Click Generate Access Token and select the Facebook Page linked to @sailjada
  • In the requested scopes section, ensure instagram_basic and pages_show_list are checked
  • Generate the token—it's valid for approximately 1 hour

Step 4: Retrieve Instagram Business Account ID

With the short-lived token, make two API calls to traverse the Facebook Page → Instagram Business Account relationship:

# First call: Get the Facebook Page ID and Instagram Business Account reference
# Note the returned page_id for the next call

# Second call: Extract the instagram_business_account ID
# The "id" field inside instagram_business_account becomes IG_USER_ID

Store the IG_USER_ID value—this is a stable identifier for @sailjada's Instagram Business Account and won't change.

Step 5: Exchange for Long-Lived Token

Short-lived tokens expire in ~1 hour. For server-side Lambda execution, we need a long-lived token (60-day validity). Using your app credentials (APP_ID and APP_SECRET from App Dashboard → Settings → Basic), make an exchange call:

# Exchange call structure (do NOT include APP_SECRET in logs or version control)
# Returns a new access_token valid for ~60 days
# Store this as IG_ACCESS_TOKEN in Lambda environment

Infrastructure: Lambda Environment Variables

The shipcaptaincrew function expects two environment variables for Instagram functionality:

  • IG_USER_ID — The Instagram Business Account ID (e.g., "17841401234567890")
  • IG_ACCESS_TOKEN — The long-lived access token (expires in 60 days)

Update these using the AWS CLI:

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

Once set, the Lambda function's dormant Instagram code path activates. When a guest requests `/g/2026-04-29`, the function queries Instagram Graph API for media posted within that event's time window and merges results with approved guest photos.

Architecture Pattern: Time-Windowed Media Aggregation

The guest photo page follows an event-centric aggregation pattern:

  1. Request parsing: Extract event_id (date) from the URL path
  2. Parallel fetch: Query both the guest photo database and Instagram API concurrently
  3. Time-window filtering: Instagram API returns media, Lambda filters by the event date ± a configurable window (typically ±4 hours)
  4. Approval check: Guest photos are only included if they've passed moderation
  5. Unified response: Return merged, sorted results to the client

This pattern avoids pre-caching Instagram content, reducing complexity. Each page request is fresh, ensuring new Instagram posts appear immediately without Lambda re-deployment.

Key Decisions

Long-lived tokens over short-lived: Generating a new token on every request would require storing APP_SECRET (a security risk). Instead, we generate long-lived tokens upfront and refresh monthly as a scheduled task, keeping secrets out of the Lambda function.

Environment variables over Secrets Manager: For non-sensitive identifiers like IG_USER_ID, environment variables are simpler. IG_ACCESS_TOKEN, while an OAuth token, is not a secret key—it's ephemeral and rotation is automated. Storing it as an environment variable avoids Secrets Manager API overhead on every invocation.

Server-side filtering over API parameters: Instagram Graph API's filtering options are limited. Implementing time-window logic in Lambda (post-fetch filtering) is more flexible than relying on API query parameters.

What's Next

To maintain the 60-day token lifecycle:

  • Set up an EventBridge rule to trigger a token-refresh Lambda function monthly
  • The refresh function uses APP_ID and APP_SECRET (stored in AWS Secrets Manager, not Lambda) to exchange the current token for a fresh 60-day token
  • Update the shipcaptaincrew Lambda's IG_ACCESS_TOKEN environment variable