Integrating Instagram Graph API with Lambda: Adding Social Media Context to Guest Photo Galleries
What Was Done
We integrated Instagram's Graph API into the shipcaptaincrew Lambda function (us-east-1, account 782785212866) to enrich guest photo gallery pages with curated social media content. The guest photo system at shipcaptaincrew.queenofsandiego.com/g/{event_id} (e.g., /g/2026-04-29) previously displayed only approved user-uploaded photos. Now it seamlessly blends those uploads with Instagram posts from @sailjada captured during the same event window, creating a unified narrative without duplicate moderation overhead.
The Instagram integration was already architecturally present in the Lambda codebase but dormant—it returned an empty array when environment variables were missing. Our task was to provision the correct OAuth tokens and configure the Lambda environment to activate this functionality.
Technical Details: OAuth Token Provisioning
Why this approach: Instagram's Graph API requires long-lived access tokens (valid for 60 days) rather than short-lived ones (1 hour). This allows the Lambda to make unauthenticated calls to fetch media metadata without token refresh logic on every invocation, reducing latency and complexity. The trade-off is token rotation every 60 days, which we'll automate via EventBridge.
Step 1: Correct Product Configuration
The initial blocker was a misconfigured app product. The sailjada-social app on Facebook Developers had the "Messaging" use case added, which grants DM-related scopes but explicitly excludes instagram_basic—the scope required to read media. We corrected this by:
- Navigating to developers.facebook.com/apps
- Selecting the
sailjada-socialapplication - Clicking Add Product in the left sidebar
- Finding and configuring Instagram Graph API (distinct from Basic Display or Messaging)
This product configuration alone does not grant tokens—it merely enables the API surface and allows the app to request the necessary OAuth scopes during token generation.
Step 2: Account Connection
Instagram's Graph API requires the business/creator account to be connected within the app's API setup interface. We verified that @sailjada is configured as a Creator account (not a personal account) and linked to a Facebook Business Page. Then, within Instagram Graph API settings, we triggered the Add Instagram Account flow, authenticating as @sailjada to establish the connection.
Step 3: Short-Lived Token Generation
Using the Graph API Explorer, we generated an initial short-lived token with the required scopes:
Scopes requested: instagram_basic, pages_show_list
Selected app: sailjada-social
Selected page: (Facebook Business Page linked to @sailjada)
The Graph API Explorer is a browser-based tool that simplifies OAuth without building a full OAuth redirect flow. For production token refresh (described later), we'll use direct HTTP calls.
Step 4: Retrieving IG_USER_ID
The short-lived token grants access to Facebook Page endpoints. We used two sequential calls to extract the Instagram Business Account ID:
# Call 1: Get the Facebook Page's connected Instagram Business Account
curl -s "https://graph.instagram.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token={SHORT_LIVED_TOKEN}"
# Response contains: { "instagram_business_account": { "id": "..." } }
# Call 2: Verify account and retrieve media capabilities
curl -s "https://graph.instagram.com/v18.0/{IG_USER_ID}?fields=id,username,name&access_token={SHORT_LIVED_TOKEN}"
The id field from the instagram_business_account object becomes IG_USER_ID. This ID is stable and tied to the @sailjada account permanently.
Step 5: Long-Lived Token Exchange
Short-lived tokens expire in 1 hour, making them unsuitable for Lambda environment variables. We exchanged it for a long-lived token (valid 60 days) using the app credentials:
curl -s "https://graph.instagram.com/v18.0/oauth/access_token" \
-X POST \
-d "grant_type=ig_refresh_token" \
-d "access_token={SHORT_LIVED_TOKEN}" \
-d "client_id={APP_ID}" \
-d "client_secret={APP_SECRET}"
# Response: { "access_token": "...", "token_type": "bearer" }
The returned access_token is the long-lived token suitable for environment configuration. It will not expire for 60 days, after which the same exchange call renews it.
Infrastructure: Lambda Environment Configuration
Once tokens were obtained, we updated the shipcaptaincrew Lambda function configuration in us-east-1 with two environment variables:
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables='{
"IG_USER_ID":"(extracted from step 4)",
"IG_ACCESS_TOKEN":"(long-lived token from step 5)"
}'
The Lambda code (unchanged) reads these variables and constructs Graph API calls to fetch media metadata. When both variables are present and valid, it now returns an array of Instagram posts alongside guest-uploaded photos in the JSON response.
Key Decisions
- Long-lived tokens over refresh logic: Embedding token refresh inside Lambda would require storing
APP_SECRETas an environment variable (security risk) and adds latency per invocation. Long-lived tokens shift the refresh burden to a monthly EventBridge rule, which is safer and simpler. - Graph API v18.0: We used the latest stable version available at provisioning time. Facebook regularly deprecates older versions; we'll monitor release notes and plan upgrades semi-annually.
- No pagination in initial rollout: The Lambda currently fetches the most recent 10 posts by default. If
@sailjada's posting volume increases significantly, we'll add pagination and result filtering by timestamp to ensure consistent performance. - Separate product from Messaging: While tempting to reuse one app product, OAuth scope combinations are locked per product. Mixing Messaging with Graph API Graph API would have either failed or leaked unintended permissions.
What's Next
We've scheduled a review to implement monthly token refresh via EventBridge. A ScheduleGroup rule (cron: 0 0 1 * ? *) will invoke a token-refresh Lambda that calls the exchange endpoint and updates the shipcaptaincrew function's environment variables automatically, eliminating manual intervention. We'll also add CloudWatch alarms to detect Instagram API errors (rate limits, connection failures) and surface them to the ops dashboard.
Once this is validated in production at /g/2026-04-29