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
@sailjadafetched 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