Integrating Instagram Graph API with AWS Lambda for Event Photo Galleries
We recently implemented Instagram media integration into the ShipCaptainCrew guest photo gallery system, enabling dynamic display of @sailjada Instagram posts alongside user-uploaded charter photos. This post walks through the architecture decisions, infrastructure setup, and implementation details for integrating Meta's Instagram Graph API with a Lambda-based backend.
System Overview
The ShipCaptainCrew tool at shipcaptaincrew.queenofsandiego.com/g/{event_id} serves as a guest photo sharing platform for charter events. The original design displayed approved user uploads; we extended it to enrich the experience by fetching Instagram posts from the same event date/time window, creating a cohesive media feed without requiring guests to manually curate or cross-post content.
- Backend: AWS Lambda function (
shipcaptaincrew) in us-east-1, account 782785212866 - Frontend: Static HTML/JS at
/tools/shipcaptaincrew/index.html, deployed to S3 - Guest Route:
/g/{event_id}queries the Lambda for both approved photos and Instagram media - Authentication: Instagram Graph API with long-lived access tokens stored as Lambda environment variables
Instagram Graph API Setup: The Critical First Step
Meta's developer ecosystem is complex, and choosing the right product configuration is essential. We initially configured a "Messaging" use case, which grants DM-related scopes but not the instagram_basic scope required to read media. This is a common pitfall.
The correct approach:
- Navigate to
developers.facebook.com/appsand select thesailjada-socialapplication - In the left sidebar, click Add Product (near the bottom)
- Search for "Instagram" and select Instagram Graph API (not Basic Display, not Messaging)
- Complete the setup flow, which adds Instagram Graph API to your product list
Why this matters: Instagram Graph API grants access to instagram_basic and related scopes needed to query media, insights, and account info. Messaging and Basic Display are specialized use cases with restricted scope sets.
Token Generation and Lifecycle Management
Instagram Graph API tokens follow a two-tier model:
- Short-lived tokens (1 hour): Generated via the Graph API Explorer or OAuth flows, used for development and debugging
- Long-lived tokens (60 days): Exchanged from short-lived tokens, used in production and refreshed periodically
Token exchange flow:
# Exchange a short-lived token for a long-lived token
curl -X GET "https://graph.instagram.com/access_token" \
-d "grant_type=fb_exchange_token" \
-d "client_id={APP_ID}" \
-d "client_secret={APP_SECRET}" \
-d "access_token={SHORT_LIVED_TOKEN}"
The returned access_token is valid for 60 days. We established a monthly refresh cadence using EventBridge to call a Lambda maintenance function before expiry, ensuring uninterrupted service.
Lambda Implementation: Dormant-by-Default Design
The Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py was designed to gracefully degrade when Instagram credentials are absent:
def get_instagram_posts(event_id, window_hours=12):
ig_user_id = os.getenv('IG_USER_ID')
ig_token = os.getenv('IG_ACCESS_TOKEN')
if not ig_user_id or not ig_token:
return [] # Return empty array; no Instagram integration
# Proceed with Graph API calls to fetch media
# ...
This pattern was intentional: the feature is production-ready but disabled until credentials are configured. It allows the guest photo gallery to function independently without external API dependencies, reducing failure points during the initial rollout.
Infrastructure: Environment Variables and Secrets
Two environment variables control Instagram integration in the Lambda function configuration:
- IG_USER_ID: The Instagram Business Account ID (retrieved via the Graph API)
- IG_ACCESS_TOKEN: The long-lived access token (rotated every 60 days)
To retrieve IG_USER_ID: Query the Facebook Page connected to @sailjada, then extract the instagram_business_account ID:
# Step 1: Get the Facebook Page
curl -X GET "https://graph.facebook.com/me/accounts" \
-d "fields=id,name" \
-d "access_token={TOKEN}"
# Step 2: Get the Instagram Business Account from the page
curl -X GET "https://graph.facebook.com/{PAGE_ID}" \
-d "fields=instagram_business_account" \
-d "access_token={TOKEN}"
# The returned "instagram_business_account.id" is IG_USER_ID
To update the Lambda:
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables='{IG_USER_ID={YOUR_ID},IG_ACCESS_TOKEN={YOUR_TOKEN}}'
Verification and Testing
Once credentials are configured, test the integration by visiting a known event:
curl -s "https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29" | grep -i instagram
Check Lambda logs to confirm API calls and token validity:
aws logs tail /aws/lambda/shipcaptaincrew --region us-east-1 --follow
Key Architectural Decisions
- Graceful degradation: Instagram integration is optional; the gallery functions without it, reducing coupling to Meta's APIs
- Long-lived tokens: Chosen over short-lived flows to minimize OAuth redirects and streamline server-to-server communication
- Environment variables: Secrets stored in Lambda configuration, not in code or S3, following AWS best practices
- Event-driven refresh: EventBridge triggers monthly token exchanges, eliminating manual credential rotation
What's Next
The foundation is in place for broader Instagram integration. Future enhancements include:
- Caching Instagram posts in DynamoDB to reduce API throttling and improve response times
- Filtering by hashtag or location to ref