Integrating Instagram Graph API with AWS Lambda: Adding Social Media Context to Guest Photo Galleries

What Was Done

We implemented Instagram Graph API integration into the shipcaptaincrew Lambda function (us-east-1, account 782785212866) to surface @sailjada Instagram posts alongside guest-uploaded charter photos on the guest gallery pages at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The integration was previously dormant due to missing environment variable configuration. This work involved setting up proper Instagram API credentials, configuring long-lived access tokens, and establishing a token refresh strategy.

Technical Details: Instagram Graph API Authentication Flow

Why Instagram Graph API (not Basic Display)? The Basic Display API only returns captions and media URLs—no timestamp or engagement metadata. The Graph API provides structured media objects with creation timestamps, which we need to filter posts by event date/time windows. This decision saves us from post-processing Instagram data against a separate metadata store.

Step 1: Product Setup in Facebook App Dashboard

The initial configuration mistake was adding the "Messaging" use case under Instagram products. Messaging is designed for Direct Message handling and does not grant the instagram_basic scope required to read media. The fix:

  • Navigate to developers.facebook.com/apps
  • Select the sailjada-social app
  • Click Add Product in the left sidebar
  • Search for and select Instagram Graph API (distinct from "Basic Display")
  • The product will appear in the left sidebar under "Instagram Graph API"

Why this matters: Scopes are product-specific in Facebook's permission model. Adding the right product first ensures the OAuth flow grants the correct scopes when we generate tokens.

Step 2: Account Connection

Within the Instagram Graph API product settings, we connected the @sailjada business account:

  • Go to Instagram Graph APIAPI Setup
  • Click Add Instagram Account
  • Authenticate as @sailjada (account must be Business or Creator class)

This establishes a link between the app and the Instagram account, which is required before the Graph API will return that account's media in API responses.

Step 3: Short-Lived Access Token Generation

Using the Facebook Graph API Explorer to obtain initial credentials:

  • Visit developers.facebook.com/tools/explorer
  • Select app: sailjada-social
  • Click Generate Access Token
  • Select the Facebook Page linked to @sailjada
  • Ensure granted scopes include: instagram_basic, pages_show_list

This token is short-lived (valid ~2 hours) but sufficient to make the user ID discovery calls.

Step 4: Extracting the Instagram User ID

With the short-lived token, we queried the Facebook Pages API to locate the connected Instagram business account:

curl -X GET \
  "https://graph.instagram.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token=SHORT_LIVED_TOKEN"

The response contains an instagram_business_account object with an id field—this is the IG_USER_ID we store as an environment variable. Why extract this? The Instagram Graph API requires the numeric user ID to fetch media; we cannot query by username.

Step 5: Long-Lived Token Exchange

Short-lived tokens expire too quickly for serverless Lambda invocations. We exchanged it for a long-lived token (valid 60 days):

curl -X GET \
  "https://graph.instagram.com/v18.0/access_token?grant_type=ig_exchange_token&client_id=APP_ID&client_secret=APP_SECRET&access_token=SHORT_LIVED_TOKEN"

Why 60 days instead of indefinite? Facebook's API grants 60-day long-lived tokens by design; indefinite tokens require manual refresh or explicit app-level permissions we don't need. A 60-day window aligns with quarterly infrastructure reviews and gives us a natural cadence for token rotation.

Infrastructure: Lambda Configuration

The shipcaptaincrew Lambda function (us-east-1) stores credentials as environment variables:

  • IG_USER_ID — the numeric Instagram business account ID
  • IG_ACCESS_TOKEN — the long-lived token (60-day validity)

Update command (executed via AWS CLI with proper role credentials):

aws lambda update-function-configuration \
  --function-name shipcaptaincrew \
  --region us-east-1 \
  --environment Variables={IG_USER_ID=YOUR_IG_USER_ID,IG_ACCESS_TOKEN=YOUR_LONG_LIVED_TOKEN}

The Lambda code checks for these variables at runtime; if either is missing or empty, the Instagram integration returns an empty array (graceful degradation). This design allows the photo gallery to function without Instagram data while we stage the token setup.

Token Refresh Strategy

Since tokens expire every 60 days, we have two options:

  1. Manual refresh: Repeat the long-lived token exchange call quarterly and update Lambda env vars via the CLI above.
  2. Automated refresh: Create an EventBridge rule that triggers a utility Lambda every 45 days to exchange and update the function configuration. This requires the utility Lambda to have lambda:UpdateFunctionConfiguration IAM permissions.

For now, we're implementing manual refresh with calendar reminders, as token refresh is infrequent enough that automation overhead isn't justified. If this pattern scales to multiple social accounts, we'll revisit automation.

Key Decisions and Why

  • Environment variables over Secrets Manager: These are not cryptographic secrets—they're configuration. Secrets Manager adds latency and cost for non-sensitive data. Environment variables are encrypted at rest in Lambda and decrypted at function invocation.
  • Long-lived tokens in env vars: Tokens are bearer credentials and should not be logged or committed to version control. Lambda's encrypted env vars with IAM-gated function access provide sufficient security for this use case.
  • Graph API over Basic Display: As noted, we needed timestamp data for filtering. Graph API is the correct tool despite slightly higher complexity.

Verification

After updating Lambda configuration, test the integration by visiting a guest gallery page:

https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29

If successful, you'll see both guest-uploaded photos and @sailjada Instagram posts from that date rendered in the gallery. Check CloudWatch Logs for the shipcaptaincrew function to debug any API errors (invalid token, missing IG_USER