Integrating Instagram Graph API with AWS Lambda: Enabling Dynamic Social Media Display on Guest Photo Pages

Overview: What Was Done

We activated Instagram Graph API integration within an existing AWS Lambda function that powers the guest photo gallery system at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The integration enables the page to display both user-uploaded charter photos and official @sailjada Instagram posts from matching time windows, creating a unified social proof experience without requiring manual curation.

The Lambda function (shipcaptaincrew, deployed in us-east-1, account 782785212866) previously contained dormant Instagram integration code that returned empty arrays due to missing environment variables. This post documents the complete setup process to activate that functionality.

Technical Architecture: How It Works

The guest photo system follows a tiered content strategy:

  • Layer 1 (Primary): Guest-uploaded photos stored in an S3 bucket, pre-approved by site administrators and indexed in a DynamoDB table
  • Layer 2 (Secondary): Instagram posts from @sailjada fetched via Graph API, filtered by event date and time window
  • Layer 3 (Presentation): Frontend JavaScript merges both sources, deduplicates, and renders in chronological order

The Lambda function receives an event_id parameter (e.g., 2026-04-29

Infrastructure: Facebook App Configuration

Why Instagram Graph API instead of Basic Display? Basic Display is read-only and lacks the filtering/pagination capabilities needed to efficiently query by date range. Graph API provides media endpoints with query parameters, scheduled post information, and insights—features essential for a time-windowed gallery.

Setup requires a Facebook App (app ID: 1688884572116630, registered as sailjada-social) with the Instagram Graph API product explicitly added. This differs from Basic Display or Messaging products and must be added separately in the app dashboard under Add Product → Instagram Graph API.

The @sailjada Instagram account must be:

  • Converted to a Business or Creator account (not Personal)
  • Linked to a Facebook Page within the same business account
  • Connected to the app via Instagram Graph API → API Setup with Instagram Login

Authentication Flow: Token Generation and Refresh

Instagram Graph API uses time-limited access tokens. The setup involves three token exchanges:

Step 1: Generate Short-Lived Token

Using Facebook's Graph API Explorer (developers.facebook.com/tools/explorer), select the sailjada-social app and generate an access token with scopes instagram_basic and pages_show_list. This token is valid for approximately 2 hours and is used only to bootstrap the process.

Step 2: Retrieve Instagram User ID

With the short-lived token, query the Facebook Page's Instagram Business Account:

curl -s "https://graph.facebook.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token={SHORT_LIVED_TOKEN}"

The response contains instagram_business_account.id—this is IG_USER_ID, a permanent identifier for the @sailjada account within Graph API.

Step 3: Exchange for Long-Lived Token

Exchange the short-lived token for a long-lived token valid for approximately 60 days:

curl -s "https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&fb_exchange_token={SHORT_LIVED_TOKEN}"

The returned access_token is IG_ACCESS_TOKEN, deployed as an environment variable to Lambda.

Why 60-day tokens? Instagram doesn't support indefinite tokens for security reasons. A 60-day window allows monthly refresh via an EventBridge rule that re-runs the exchange step—a pattern we implemented to eliminate manual token rotation work.

Lambda Configuration and Environment Variables

Update the shipcaptaincrew Lambda function configuration with:

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

The Lambda handler (located at sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py, lines 1060–1120) checks for these variables at invocation time. If present, it constructs a query to Graph API's media endpoint:

GET /v18.0/{IG_USER_ID}/media?fields=id,caption,media_type,media_url,timestamp&access_token={IG_ACCESS_TOKEN}

Results are filtered client-side to match the event's date range, then merged with guest photos in the response payload.

Why This Architecture?

  • Separation of Concerns: Guest photos and Instagram posts are fetched independently, allowing either source to fail gracefully without breaking the page
  • DynamoDB for Guest Photos: Indexed by event ID and approval status, enabling fast queries without scanning S3 directory listings
  • Long-Lived Tokens in Environment: Avoids storing credentials in code and leverages Lambda's native secrets management through AWS Systems Manager Parameter Store (optional upgrade)
  • Client-Side Filtering: Reduces Lambda memory footprint and allows frontend logic to handle timezone-aware date ranges

Key Decisions and Rationale

Why not use Basic Display? It lacks date-range filtering; all queries return the entire media library, requiring client-side pagination and multiple API calls. Graph API's filtered endpoint is far more efficient.

Why store tokens in Lambda environment? For simplicity in this implementation. A production system might use AWS Secrets Manager for additional audit logging and rotation capabilities, but environment variables are sufficient here given token lifespan and refresh cadence.

Why EventBridge for refresh? A scheduled rule (set to trigger every 30 days) calls a Lambda function that re-runs the exchange step, ensuring tokens never expire mid-event. This eliminates on-call burden and prevents gallery outages.

Testing and Verification

After deploying environment variables, verify by visiting:

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

Check CloudWatch Logs for the /aws/lambda/shipcaptaincrew log group to confirm API calls are succeeding. The response should include both guest photos and Instagram posts in