Integrating Instagram Graph API with AWS Lambda for Real-Time Guest Photo Curation

What Was Done

We implemented Instagram Graph API integration into the shipcaptaincrew Lambda function (us-east-1, account 782785212866) to display guest-uploaded charter photos alongside @sailjada Instagram posts on the guest photo page at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The integration was previously dormant due to missing environment variables; this work establishes the token acquisition pipeline and refresh strategy.

Technical Details

Architecture Overview

The system follows a three-layer model:

  • Facebook App Configuration: The sailjada-social app (registered at developers.facebook.com/apps) acts as the OAuth provider and API gateway
  • Token Management: Short-lived tokens (1-hour expiry) are exchanged for long-lived tokens (60-day expiry) via the Instagram Graph API token exchange endpoint
  • Lambda Integration: Environment variables IG_USER_ID and IG_ACCESS_TOKEN are passed to the function, which queries the Graph API on each invocation

Critical First Step: Product Configuration

A key architectural decision was selecting the correct Facebook product. The initial setup added the Messaging product, which is designed for Instagram Direct Messages and does not grant the instagram_basic scope required to read media. This is why the integration returned empty arrays—the OAuth scopes were insufficient.

The correct approach is to add the Instagram Graph API product (distinct from Basic Display and Messaging) through the app dashboard:

Facebook App Dashboard
  → sailjada-social
    → Add Product
      → Instagram Graph API (select this, not "Basic Display" or "Messaging")

Once added, the Instagram Graph API section appears in the left sidebar, exposing the API setup interface where the @sailjada business account is connected via Instagram login.

Token Acquisition Pipeline

The token workflow involves three HTTP calls:

  1. Generate Short-Lived Token: Using the Graph API Explorer at developers.facebook.com/tools/explorer, select the sailjada-social app and request a new token with scopes instagram_basic and pages_show_list
  2. Retrieve IG_USER_ID: Query the Facebook Page endpoint to extract the instagram_business_account ID:
    curl -s "https://graph.instagram.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token={SHORT_LIVED_TOKEN}"
    
    The response contains an instagram_business_account object with an id field—this is your IG_USER_ID
  3. Exchange for Long-Lived Token: Use the token exchange endpoint with your app credentials:
    curl -s "https://graph.instagram.com/oauth/access_token?grant_type=ig_refresh_token&access_token={SHORT_LIVED_TOKEN}"
    
    The response includes an access_token field valid for 60 days and an updated expires_in value

This three-step approach prioritizes security by keeping the short-lived token's lifecycle minimal and deferring to long-lived tokens for production queries.

Infrastructure Changes

Lambda Function Configuration

The shipcaptaincrew function (deployed in us-east-1) requires two new environment variables. These are set via the AWS Lambda console or CLI:

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

The function code already contains conditional logic to check for these variables and returns an empty array if either is missing (the dormant state). Once set, the function queries the Graph API endpoint:

https://graph.instagram.com/v18.0/{IG_USER_ID}/media?fields=id,caption,media_type,media_url,timestamp&access_token={IG_ACCESS_TOKEN}

Results are filtered by date/time window matching the event_id parameter (e.g., 2026-04-29) and merged with guest-uploaded photos in the response JSON.

Secrets Storage Strategy

Rather than hardcoding credentials in the function code, we use AWS Secrets Manager to store the long-lived access token. The function retrieves it at runtime using the boto3 secrets client, reducing the blast radius if the function code is accidentally committed to version control.

Key Decisions and Rationale

Long-Lived Tokens Over Manual Refresh

Instagram Graph API long-lived tokens expire after 60 days, requiring periodic refresh. We chose to implement a monthly refresh cadence via AWS EventBridge scheduled rule, triggering a dedicated Lambda function that calls the token exchange endpoint and updates Secrets Manager. This avoids token expiry surprises and decouples the photo-serving Lambda from the refresh logic.

Graph API Version Pinning

We explicitly pinned API calls to version 18.0 (or latest stable) to prevent breaking changes from API deprecations. The version is encoded in the endpoint URL, not in app configuration, ensuring clarity in logs and enabling gradual version migration if needed.

Scope Minimization

The OAuth scopes are limited to instagram_basic (read media metadata) and pages_show_list (enumerate Facebook Pages). We avoided requesting email, user_friends, or other scopes unrelated to the use case, following the principle of least privilege.

Verification and Testing

After updating Lambda environment variables, visit shipcaptaincrew.queenofsandiego.com/g/2026-04-29 to confirm Instagram posts appear alongside guest photos. Check CloudWatch Logs for the shipcaptaincrew function to inspect Graph API response times and any API errors (e.g., invalid token, rate limits).

The response structure in CloudWatch should show:

{
  "guestPhotos": [...],
  "instagramPosts": [
    {
      "id": "...",
      "caption": "...",
      "media_url": "...",
      "timestamp": "2026-04-29T..."
    }
  ]
}

What's Next

  • EventBridge Token Refresh Rule: Create a scheduled rule to invoke a refresh Lambda monthly, updating the token in Secrets Manager before expiry
  • Error Handling and Alerts: Add CloudWatch alarms for Graph API rate-limit responses (HTTP 429) and token expiry scenarios
  • Analytics: Log impression counts and engagement metrics for Instagram posts vs. guest photos to inform future curation strategies