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

What Was Done

We extended the guest photo gallery system at shipcaptaincrew.queenofsandiego.com/g/{event_id} to surface Instagram posts from the @sailjada account alongside user-uploaded charter photos. The Lambda function (shipcaptaincrew in us-east-1, account 782785212866) already had dormant Instagram integration code — it was returning empty arrays because the necessary environment variables weren't configured. This post covers the complete setup process: creating the correct Facebook app product configuration, obtaining Instagram Graph API credentials, and securing those credentials in Lambda environment variables.

Technical Details: The Instagram Graph API Credential Flow

Instagram Graph API operates through a token exchange pattern that's stricter than traditional OAuth. The flow requires three distinct steps:

Step 1: Configure the Correct App Product

This is the critical first decision point. Facebook's developer dashboard offers multiple Instagram-related products with different capabilities:

  • Instagram Basic Display — Read-only access to basic profile info and media captions (legacy, being phased out)
  • Instagram Messaging — Send and receive direct messages (what you may have accidentally configured first)
  • Instagram Graph API — Full media reading with metadata, insights, and business account features

Why this matters: The Messaging product grants different OAuth scopes (primarily `instagram_manage_messages`) than what we need. Graph API is the correct choice for reading media timelines and requires scopes `instagram_basic` and `pages_show_list`.

Configuration steps:

  1. Navigate to developers.facebook.com/apps
  2. Select app sailjada-social
  3. Click Add Product in the left sidebar
  4. Search for and select Instagram Graph API
  5. Choose Instagram Graph API as the access type (not Basic Display)

Step 2: Connect the Instagram Business Account

Instagram Graph API requires that @sailjada be classified as a Business or Creator account (not a personal account) and be linked to a Facebook Page. This is a prerequisite we verify before proceeding:

  • Log into @sailjada and confirm Account Type is set to Business or Creator
  • Verify the account is connected to a Facebook Page in Instagram Settings → Account

Once confirmed, establish the connection in the app dashboard:

  1. In Instagram Graph API settings, locate API Setup with Instagram Login
  2. Click Add Instagram Account
  3. Authenticate as @sailjada
  4. Grant the requested permissions

Step 3: Generate Short-Lived Access Token

The Graph API Explorer provides a convenient way to generate the initial token without writing authentication code:

  1. Visit developers.facebook.com/tools/explorer
  2. Select app sailjada-social from the dropdown (top-right)
  3. Click Generate Access Token
  4. Select the Facebook Page linked to @sailjada
  5. Ensure requested scopes include instagram_basic and pages_show_list

This generates a short-lived token (valid ~2 hours) used only for the next step.

Step 4: Retrieve Instagram User ID

The IG_USER_ID (technically the Instagram Business Account ID) is obtained through two sequential API calls:

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

The response contains instagram_business_account.id — save this value as IG_USER_ID.

Step 5: Exchange for Long-Lived Token

Short-lived tokens expire quickly. We exchange it for a long-lived token (valid 60 days) using the app credentials:

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

The returned access_token is the value we store as IG_ACCESS_TOKEN in Lambda environment variables.

Infrastructure: Lambda Configuration

The Lambda function shipcaptaincrew contains application code that attempts to read from environment variables IG_USER_ID and IG_ACCESS_TOKEN. These were previously undefined, causing the Instagram integration branch to return empty results.

Update command (after obtaining credentials from steps above):

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

Why environment variables instead of hardcoding? This follows the twelve-factor app principle — credentials are external configuration, not code. It also allows token rotation without redeploying the function.

Security consideration: AWS Lambda encrypts environment variables at rest using KMS (the default AWS-managed key). For additional control, you could specify a customer-managed KMS key in the function configuration, though the default is adequate for this use case.

Architecture Pattern: Token Refresh Strategy

Instagram's 60-day token expiration requires a refresh mechanism. Two approaches:

  • Manual refresh: Run the token exchange call monthly, update Lambda environment variables via CLI or AWS Console. Simple, no additional infrastructure, suitable if you're actively maintaining the integration.
  • Automated refresh: Create an EventBridge rule triggering a Lambda function every 50 days to refresh the token and update the main function's environment. More complex but requires no manual intervention.

For initial deployment, manual refresh is recommended. The token exchange call is idempotent (same inputs always produce the same output), so running it before expiration is safe.

Verification

After updating the Lambda environment variables, the guest photo page should immediately begin displaying Instagram posts alongside user uploads:

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

The response should include both approved_photos (from your upload system) and instagram_posts (from @sailjada's timeline) in the same time window.

Key Decisions

  • Graph API product choice: Messaging was wrong; Graph API is correct for media reads.
  • Environment variables for secrets: Keeps credentials external from code; supports rotation without redeploy.
  • Manual token refresh: Reduces operational complexity compared to automated EventBridge scheduling.
  • 60-day token lifespan: Instagram's design