Integrating Instagram Graph API with AWS Lambda: Building a Guest Photo Gallery with Social Media Timeline Sync
What Was Done
We implemented Instagram Graph API integration into the shipcaptaincrew Lambda function to automatically populate a guest photo gallery page (/g/{event_id}) with both user-uploaded charter photos and matching @sailjada Instagram posts from the same event date/time window. This required connecting the existing Lambda function to Meta's Instagram Graph API, establishing proper authentication flows, and managing long-lived access tokens with automated refresh strategies.
Architecture Overview
The system operates as a three-tier integration:
- Client Layer: Static HTML at
/tools/shipcaptaincrew/index.html(served via CloudFront) with JavaScript that calls the Lambda API endpoint - API Layer: Lambda function (
shipcaptaincrew, region:us-east-1, account:782785212866) that handles guest photo retrieval and Instagram media queries - Data Layer: DynamoDB table for approved guest photos; Instagram Graph API for media metadata
The Lambda function previously had dormant Instagram integration code that returned empty arrays when environment variables IG_USER_ID and IG_ACCESS_TOKEN were missing. Our work activated this integration by properly provisioning these credentials.
Technical Implementation Details
Meta App Configuration
The first critical decision was which Meta product to enable. The existing app sailjada-social had the "Manage Messaging" use case added, which grants DM-related permissions but does not include the instagram_basic scope needed to read media metadata. We added the Instagram Graph API product (distinct from Basic Display or Messaging) to the app configuration.
Why this matters: Meta's product system grants scopes based on use case. Messaging products don't include media read permissions. Instagram Graph API is the correct product tier for reading business account media, insights, and media children.
Account Linking and Token Generation
The process requires several sequential steps:
- Connect @sailjada as a Business/Creator account to the app via Instagram Graph API settings (the account must be linked to a Facebook Page that the app can access)
- Generate a short-lived access token using the Facebook Graph API Explorer:
- Visit
developers.facebook.com/tools/explorer - Select the
sailjada-socialapp - Generate token for the Facebook Page linked to @sailjada
- Request scopes:
instagram_basic,pages_show_list
- Visit
- Retrieve the Instagram Business Account ID (IG_USER_ID) via two Graph API calls:
- First call gets the Page's
instagram_business_accountobject - Extract the
idfield — this is the IG_USER_ID
- First call gets the Page's
- Exchange for a long-lived token (valid for ~60 days):
- Uses the short-lived token, APP_ID, and APP_SECRET
- Returns IG_ACCESS_TOKEN for production use
Lambda Function Changes
The Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py already contained the Instagram integration logic but required activation. Key modifications:
- Environment variable setup: Added
IG_USER_IDandIG_ACCESS_TOKENto the Lambda function configuration via AWS Lambda console - No code changes needed: The existing handler checks for these variables and calls Instagram Graph API endpoints when present
- Async execution pattern: The function queries both DynamoDB (for guest photos) and Instagram Graph API in parallel, merging results by event date
Environment variables are managed via:
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables={IG_USER_ID=<value>,IG_ACCESS_TOKEN=<value>}
Token Refresh Strategy
Why long-lived tokens matter: Short-lived tokens expire in hours. Long-lived tokens last ~60 days, suitable for server-to-server integrations. However, they require periodic refresh.
We implemented a monthly refresh approach:
- Option 1 (Manual): Refresh the token using the same token exchange endpoint, update Lambda configuration
- Option 2 (Automated): Create an EventBridge scheduled rule that triggers a separate Lambda function monthly to refresh and update the main Lambda's environment variables
The refresh call requires APP_ID, APP_SECRET, and the current IG_ACCESS_TOKEN. No user interaction is needed once configured.
Infrastructure and Deployment
The Lambda function is deployed in us-east-1 and accessed via API Gateway, which routes requests from the CloudFront-distributed HTML frontend. The guest photo page route pattern is /g/{event_id}, where event_id is a date string like 2026-04-29.
Key resources:
- Lambda function:
shipcaptaincrew(runtime: Python 3.x) - DynamoDB table: Stores approved guest photos with event_id as partition key
- API Gateway: Routes HTTPS requests to Lambda
- CloudFront distribution: Caches static HTML/CSS/JS (index.html at
/tools/shipcaptaincrew/index.html) - S3 bucket: Origin for CloudFront (exact bucket name redacted for security)
The frontend JavaScript calls the Lambda API endpoint with ?event_id=2026-04-29, receives a JSON response containing both guest photo metadata and Instagram media objects, and renders them in chronological order.
Key Design Decisions
- Why not use Basic Display API? Basic Display only returns a limited set of fields (media ID, caption, timestamp, media type, media URL). Graph API returns richer metadata including insights. For a charter photo experience, Graph API is the more extensible choice.
- Why long-lived tokens instead of client-side flow? The page doesn't require user login. Server-side token exchange (Lambda) is simpler and more secure than embedding client credentials in frontend code.
- Why separate environment variables? Decoupling IG_USER_ID and IG_ACCESS_TOKEN allows independent rotation of credentials and makes it easier to audit which Lambda functions need Instagram access.
- Why query both DynamoDB and Instagram? Guest photos are user-uploaded and must be approved (stored in DynamoDB). Instagram posts are