```html

Integrating Instagram Graph API with AWS Lambda: Connecting @sailjada Social Media to Guest Photo Events

What Was Done

We integrated Instagram's Graph API into the shipcaptaincrew Lambda function (us-east-1, account 782785212866) to surface @sailjada Instagram posts alongside guest-uploaded charter photos on the guest photo page at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The Lambda was already structured to accept Instagram data via environment variables (IG_USER_ID and IG_ACCESS_TOKEN), but those credentials were missing and the integration was dormant. This walkthrough covers the complete path from Facebook app configuration through Lambda deployment.

Why This Architecture

Rather than polling Instagram at request time, we opted to store credentials in Lambda environment variables and fetch media within the function. This approach:

  • Reduces latency: No external API round-trip during page render; media fetch happens server-side in Lambda and is bundled with guest photo responses.
  • Simplifies caching: We can layer CloudFront or Lambda response caching without managing Instagram rate limits on the client.
  • Keeps secrets centralized: Credentials live in AWS Secrets Manager and are injected as environment variables at function initialization, not hardcoded.
  • Decouples Instagram from frontend: The guest photo page receives a unified JSON response with both guest and Instagram media, no cross-origin calls needed.

Technical Details: The Instagram Graph API Setup

Step 1: Add the Correct Product to Your Facebook App

The initial blocker was that the sailjada-social app had the Instagram Messaging product added (for DM functionality), but this product doesn't grant the instagram_basic scope required to read media. We needed to add the Instagram Graph API product separately:

  • Navigate to developers.facebook.com/apps
  • Open the sailjada-social app
  • In the left sidebar, click Add Product
  • Search for and select Instagram Graph API (distinct from Instagram Basic Display)
  • Complete the setup flow; Instagram Graph API now appears in your product sidebar

Why this matters: Each product in Facebook's developer ecosystem grants different permissions. Messaging is for user-to-user communication; Graph API is for accessing public account data (posts, media, insights). Attempting to use Messaging scopes for media reads will silently fail or return empty arrays.

Step 2: Confirm Account Setup and Generate Tokens

The @sailjada Instagram account must be a Business or Creator account and linked to a Facebook Page. Once verified:

  • In Instagram Graph API settings, click API setup with Instagram login
  • Click Add Instagram account and authenticate as @sailjada
  • Navigate to developers.facebook.com/tools/explorer
  • Select app sailjada-social from the dropdown
  • Click Generate Access Token
  • In the permissions dialog, select the Facebook Page linked to @sailjada
  • Request scopes: instagram_basic, pages_show_list

This generates a short-lived token (valid ~2 hours) used only to bootstrap the process.

Step 3: Retrieve IG_USER_ID via Graph API Calls

With the short-lived token, make two API calls to locate the Instagram Business Account ID:

curl -s "https://graph.instagram.com/me?fields=id,name&access_token=SHORT_LIVED_TOKEN"
# Returns your Facebook Page ID; note it

curl -s "https://graph.instagram.com/{PAGE_ID}?fields=instagram_business_account&access_token=SHORT_LIVED_TOKEN"
# Returns: {"instagram_business_account":{"id":"XXXXXXXXX"}}
# That ID value = IG_USER_ID

Why two calls: The Graph API enforces role-based access. Your app's token is tied to your Page, not directly to the Instagram account. We must traverse: Token → Page → Instagram Business Account.

Step 4: Exchange for Long-Lived Token

The short-lived token expires in hours. Exchange it for a long-lived token (60 days) using your app credentials:

curl -s "https://graph.instagram.com/oauth/access_token" \
  -d "grant_type=fb_exchange_token" \
  -d "client_id=YOUR_APP_ID" \
  -d "client_secret=YOUR_APP_SECRET" \
  -d "access_token=SHORT_LIVED_TOKEN"
# Returns: {"access_token":"LONG_LIVED_TOKEN","token_type":"bearer"}
# That access_token value = IG_ACCESS_TOKEN

Store both IG_USER_ID and IG_ACCESS_TOKEN securely.

Infrastructure: Lambda Configuration

Update the shipcaptaincrew Lambda function with the credentials:

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

The Lambda code in the function already checks for these environment variables:

ig_user_id = os.environ.get('IG_USER_ID')
ig_access_token = os.environ.get('IG_ACCESS_TOKEN')
if ig_user_id and ig_access_token:
    # Fetch recent media from IG
    ig_media = fetch_instagram_media(ig_user_id, ig_access_token)
else:
    ig_media = []

Once environment variables are set, the function's dormant Instagram integration activates and returns media for the guest photo page.

Token Refresh Strategy

Long-lived tokens expire after 60 days. Rather than manual rotation, implement automated refresh via EventBridge:

  • Create an EventBridge rule named instagram-token-refresh
  • Schedule: cron(0 0 1 * ? *) (first of each month at UTC midnight)
  • Target: Lambda function that calls the exchange endpoint and updates shipcaptaincrew environment variables
  • This refresh function uses your app credentials (stored in AWS Secrets Manager) to rotate the token automatically

Why EventBridge over cron: EventBridge integrates natively with Lambda and CloudWatch Logs for monitoring. It's AWS-native and doesn't require external tooling or EC2 instances.

Key Decisions and Rationale

  • Environment variables over Secrets Manager: For non-sensitive tokens (Instagram's long-lived tokens are rotated frequently and not equivalent to passwords), environment variables provide faster access and simpler Lambda configuration. Secrets Manager would add latency and complexity without meaningful security gain in this case.