Integrating Instagram Graph API with AWS Lambda: Connecting @sailjada's Guest Photo Feed
What Was Done
We enabled Instagram media integration for the guest photo page system at shipcaptaincrew.queenofsandwick.com/g/{event_id} by configuring the Instagram Graph API within the Facebook Developer app and establishing a token exchange flow to populate the Lambda function with authentication credentials. This allows the existing Lambda function to fetch and display @sailjada's Instagram posts alongside user-uploaded charter photos from matching dates and times.
The Problem
The shipcaptaincrew Lambda function (deployed in us-east-1, AWS account 782785212866) contained dormant Instagram integration code that returned an empty array whenever the environment variables IG_USER_ID and IG_ACCESS_TOKEN were missing. The function logic was sound—it filters Instagram media by timestamp to match the event date—but lacked the credentials to authenticate against the Instagram Graph API.
Technical Architecture
Lambda Function Structure
The shipcaptaincrew function accepts event parameters including event_id (formatted as YYYY-MM-DD), queries the guest photo database, and conditionally fetches Instagram media when credentials are present. The Instagram integration pattern uses:
- Environment variables:
IG_USER_ID(the Instagram Business Account ID) andIG_ACCESS_TOKEN(a long-lived Graph API token with 60-day expiration) - Boto3 Lambda client: Updates function configuration via
update_function_configuration()to inject credentials securely - Graph API endpoints: The function will call
https://graph.instagram.com/{IG_USER_ID}/mediawith the access token to retrieve recent posts
Why This Approach?
Rather than hardcoding credentials in the function code or storing them in AWS Secrets Manager (which adds API call latency), we use Lambda environment variables because:
- They're encrypted at rest using the default AWS KMS key for the account
- They're injected at function invocation time with minimal overhead
- They can be rotated without redeploying function code
- The 60-day token expiration naturally forces a monthly refresh cadence, reducing token compromise risk
Facebook Developer App Configuration
Step 1: Add the Instagram Graph API Product
The first critical mistake to avoid: the existing Messaging API product (added for DM use cases) does not grant the instagram_basic scope required to read media. You must add Instagram Graph API separately:
- Navigate to
developers.facebook.com/apps - Select the
sailjada-socialapp - In the left sidebar, scroll to the bottom and click Add Product
- Search for "Instagram" and select Instagram Graph API (not Basic Display, not Messaging)
- Complete the setup flow and the product will appear in your sidebar
Step 2: Link the @sailjada Account
The @sailjada Instagram account must be a Business or Creator account and linked to a Facebook Page. Within Instagram Graph API → API setup, click "Add Instagram account" and authenticate as @sailjada. This establishes the relationship between the Facebook Page and the Instagram Business Account.
Step 3: Generate a Short-Lived Access Token
Using the Graph API Explorer at developers.facebook.com/tools/explorer:
- Select the
sailjada-socialapp from the dropdown - Click "Generate Access Token"
- Select the Facebook Page linked to @sailjada
- Request scopes:
instagram_basicandpages_show_list - Copy the generated token (valid for ~2 hours)
Credential Extraction and Token Exchange
Retrieving IG_USER_ID
Make two sequential Graph API calls with the short-lived token:
# First: Get the Facebook Page ID
curl -s "https://graph.facebook.com/v18.0/me/accounts?access_token=TOKEN" | jq '.data[] | select(.name=="[Your Page Name]") | .id'
# Second: Get the Instagram Business Account ID from the Page
curl -s "https://graph.facebook.com/v18.0/PAGE_ID?fields=instagram_business_account&access_token=TOKEN" | jq '.instagram_business_account.id'
The second response's id field is your IG_USER_ID. Store this value separately—it doesn't change.
Exchanging for a Long-Lived Token
Short-lived tokens expire quickly. Exchange it for a long-lived token (60-day validity) using your app credentials:
curl -s "https://graph.instagram.com/access_token" \
-d "grant_type=ig_refresh_token" \
-d "access_token=SHORT_LIVED_TOKEN" | jq '.access_token'
This returns IG_ACCESS_TOKEN, valid for approximately 60 days.
Lambda Deployment
Updating Environment Variables
With both credentials obtained, inject them into the Lambda function:
aws lambda update-function-configuration \
--region us-east-1 \
--function-name shipcaptaincrew \
--environment Variables="{IG_USER_ID=VALUE,IG_ACCESS_TOKEN=VALUE}"
The function is now configured to fetch Instagram media on invocation.
Key Design Decisions
- Environment variables over Secrets Manager: Reduces API latency and simplifies rotation logic for tokens with known expiration windows
- Long-lived tokens (60-day): Balances security (frequent rotation) against operational overhead (no complex refresh logic needed in the Lambda itself)
- Monthly manual refresh: A scheduled task (could use EventBridge) to execute the token exchange call 55 days after each generation prevents unexpected expirations
- Timestamp-based filtering in Lambda: The function filters Instagram posts by event date rather than relying on Instagram's built-in pagination, ensuring exact temporal alignment with guest photos
Verification and Testing
After deployment, test the integration by navigating to the guest photo page URL with a known event date:
https://shipcaptaincrew.queenofsandwick.com/g/2026-04-29
The page should now display both guest-uploaded photos and @sailjada's Instagram posts from that date in the same gallery view. Check CloudWatch Logs for the shipcaptaincrew function (log group: /aws/lambda/shipcaptaincrew) to verify the Graph API calls succeed and media is returned.
What's Next
Future enhancements should