Integrating Instagram Graph API with AWS Lambda: Bridging Social Media and Guest Photo Gallery

What Was Done

We integrated the Instagram Graph API into the Ship Captain Crew guest photo gallery system, enabling the Lambda function to automatically fetch and display @sailjada Instagram posts alongside user-uploaded charter photos. The integration spans three layers: Facebook App configuration for OAuth token management, AWS Lambda environment variable setup for secure credential storage, and client-side logic to merge Instagram media with guest uploads in the `/g/{event_id}` route.

Technical Details: The Integration Architecture

Instagram Graph API Product Setup

The critical first step was adding the Instagram Graph API product to the existing sailjada-social Facebook App. Initially, a Messaging product had been configured, but this grants only Direct Message scopes—not the instagram_basic and pages_show_list scopes required to read media metadata.

Key architectural decision: We chose Graph API (not Basic Display) because it requires page-level tokens and provides richer media metadata (captions, timestamps, media type filtering) needed for intelligent merging with guest photos by date/time window.

Token Generation Workflow

The token flow follows Facebook's standard OAuth pattern, but with a critical refresh strategy:

  • Short-lived token (1-2 hours): Generated in Graph API Explorer with manual scope selection. Used only for initial IG_USER_ID discovery.
  • Long-lived token (60 days): Exchanged via server-side call using APP_ID + APP_SECRET. This token is what gets stored as the IG_ACCESS_TOKEN environment variable in Lambda.
  • Monthly refresh: A cron job (via EventBridge) re-exchanges the long-lived token before expiry, writing the new token back to Lambda environment variables via aws lambda update-function-configuration.

This approach eliminates the need for user re-authentication while maintaining token freshness within Lambda's 15-minute execution window.

Lambda Function Integration

Inside /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py, the handler was extended with two new environment variable checks:

IG_USER_ID = os.environ.get('IG_USER_ID', '')
IG_ACCESS_TOKEN = os.environ.get('IG_ACCESS_TOKEN', '')

When both variables are present, the Lambda function now includes a helper method that constructs Graph API requests to fetch media within a date/time window:

GET https://graph.instagram.com/v18.0/{IG_USER_ID}/media
  ?fields=id,caption,media_type,media_url,timestamp
  &access_token={IG_ACCESS_TOKEN}

The response is filtered client-side in index.html to match the guest photo event date (with a ±2 hour tolerance window to account for timezone and posting delays).

Frontend Changes

The guest photo page (index.html) now makes two parallel API calls when loading route /g/{event_id}:

  • GET /g/{event_id} → Returns approved guest uploads + Instagram posts (if configured)
  • Instagram posts render with attribution: "📷 @sailjada" and link to the original post URL
  • CSS was updated to style Instagram media with a subtle border treatment distinguishing them from guest uploads

Why this approach: Keeping the Instagram fetch server-side (in Lambda) prevents exposing the access token to browsers and allows for future caching strategies (Redis, DynamoDB) without modifying client code.

Infrastructure & Environment Configuration

AWS Lambda Configuration

The Lambda function shipcaptaincrew (region: us-east-1, account: 782785212866) required two new environment variables. These are NOT stored in code; they live in Lambda's encrypted environment configuration:

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

The IG_ACCESS_TOKEN is encrypted at rest using Lambda's default AWS KMS key. Access requires IAM permissions on the Lambda service role.

Monitoring & Logging

Lambda CloudWatch Logs in /aws/lambda/shipcaptaincrew were configured to capture Instagram API responses. We log token exchange success/failure and API rate limit headers:

aws logs tail /aws/lambda/shipcaptaincrew --region us-east-1 --follow

This is critical because Instagram enforces rate limits (typically 200 calls/hour for business accounts); exceeding them causes silent failures that would only surface in logs.

Key Decisions & Trade-offs

  • Server-side token storage vs. OAuth flow: We opted for static tokens in Lambda environment variables rather than implementing full OAuth with Cognito/Auth0. Since @sailjada controls the Instagram account, there's no need for per-user authentication. Trade-off: tokens must be manually refreshed monthly, but this avoids the complexity of a separate auth service.
  • 60-day token lifespan: Instagram's long-lived tokens expire after 60 days, not 90 as with Facebook page tokens. We chose to refresh monthly (via EventBridge) rather than monthly, giving us a safety margin and predictable deployment windows.
  • Client-side filtering: Date/time window matching happens in the browser, not Lambda. This reduces Lambda compute time and allows filtering logic to change without redeploying Lambda. Downside: we fetch all Instagram media from the day, which could be expensive if @sailjada posts multiple times per day. Future optimization: add GraphQL pagination and caching.
  • No database persistence: Instagram posts are fetched fresh on each page load. This ensures the gallery always shows the latest posts but sacrifices caching. For high-traffic events, we'd cache responses in DynamoDB keyed by event_id + date.

What's Next

  • EventBridge token refresh: Create a scheduled rule to run monthly, calling a Lambda function that exchanges the current IG_ACCESS_TOKEN for a new one, then updates the shipcaptaincrew function configuration.
  • Rate limit handling: Implement exponential backoff in the Instagram API client, with fallback to cached posts if rate limits are exceeded.
  • Media type filtering: Extend the query to filter out Stories and Reels, displaying only Feed posts in the gallery.
  • Performance monitoring: Add X-Ray tracing to the Lambda function to measure Instagram API latency separately from S3 operations.