Automate Twitch follower reports using GET /helix/channels/followers with a user-access token carrying the moderator:read:followers scope — without it you only get total count, not the follower list. The old GET /users/follows endpoint is completely removed. For real-time new-follower tracking, subscribe to channel.follow EventSub v2 (v1 is dead, PubSub decommissioned April 14, 2025). Rate limit: 800 points/minute Helix bucket. Every Helix request needs both Authorization: Bearer and Client-Id headers.
API Quick Reference
User Access Token (OAuth 2.0)
800 points/minute Helix bucket
JSON
Available
Understanding the Twitch Helix API for Follower Reports
The Twitch Helix REST API (base URL: https://api.twitch.tv/helix) is the current official API. Every Helix request must include two headers: Authorization: Bearer <token> and Client-Id: <your_client_id>. Omitting either returns 401 or 400. Tokens are issued via OAuth 2.0 and have a ~60-day lifespan.
Follower endpoint underwent a major access change enforced September 12, 2023: GET /channels/followers returns the full follower list with name, user_id, and followed_at timestamps ONLY with a user-access token from the broadcaster or one of their moderators carrying moderator:read:followers. Without this scope from the correct user, the endpoint returns only the total follower count. The old GET /users/follows endpoint is completely removed — do not use it.
For real-time follower tracking, use EventSub subscription type channel.follow v2 (v1 is deprecated). EventSub replaced PubSub, which was fully decommissioned on April 14, 2025. Webhook transport requires an App Access Token and a verified public HTTPS callback; WebSocket transport requires a User Access Token. The official documentation is at https://dev.twitch.tv/docs/api/reference/#get-channel-followers.
https://api.twitch.tv/helixSetting Up Twitch API Authentication for Follower Reports
Follower report automation requires a User Access Token from the broadcaster (or a moderator on their behalf) with the moderator:read:followers scope. This is a 3-legged OAuth flow — the channel owner must authorize your app. App Access Tokens (Client Credentials) are NOT sufficient for full follower list access. Validate user tokens hourly since users can revoke access at any time.
- 1Go to dev.twitch.tv/console/apps and click 'Register Your Application'
- 2Set the OAuth Redirect URL to your callback URL (e.g., https://yourapp.com/auth/callback)
- 3Select Client Type: Confidential (for server-side apps with secrets)
- 4Save your Client ID and Client Secret
- 5Redirect the broadcaster to: https://id.twitch.tv/oauth2/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&scope=moderator:read:followers
- 6Exchange the authorization code for tokens: POST to https://id.twitch.tv/oauth2/token
- 7Store the access token and refresh token securely — validate hourly via GET https://id.twitch.tv/oauth2/validate
- 8Get the broadcaster's user ID: GET /helix/users?login=channelname
1import requests2import os34CLIENT_ID = os.environ['TWITCH_CLIENT_ID']5CLIENT_SECRET = os.environ['TWITCH_CLIENT_SECRET']6REDIRECT_URI = os.environ['TWITCH_REDIRECT_URI']78# Step 1: Exchange auth code for tokens (after user grants permission)9def exchange_code_for_tokens(auth_code):10 r = requests.post('https://id.twitch.tv/oauth2/token', data={11 'client_id': CLIENT_ID,12 'client_secret': CLIENT_SECRET,13 'code': auth_code,14 'grant_type': 'authorization_code',15 'redirect_uri': REDIRECT_URI16 })17 tokens = r.json()18 return tokens # contains access_token, refresh_token1920# Step 2: Refresh expired token21def refresh_token(refresh_tok):22 r = requests.post('https://id.twitch.tv/oauth2/token', data={23 'client_id': CLIENT_ID,24 'client_secret': CLIENT_SECRET,25 'grant_type': 'refresh_token',26 'refresh_token': refresh_tok27 })28 return r.json()2930# Step 3: Validate token (run hourly)31def validate_token(access_token):32 r = requests.get('https://id.twitch.tv/oauth2/validate',33 headers={'Authorization': f'OAuth {access_token}'})34 return r.status_code == 2003536# Standard headers for all Helix requests37def get_headers(access_token):38 return {39 'Authorization': f'Bearer {access_token}',40 'Client-Id': CLIENT_ID41 }Security notes
- •Never expose your Client Secret in frontend code or public repositories
- •Store access tokens and refresh tokens in encrypted storage
- •Validate user tokens hourly via GET https://id.twitch.tv/oauth2/validate — users can revoke access at any time
- •Use HTTPS for your OAuth redirect URI — Twitch rejects HTTP redirect URIs
- •Implement token refresh before expiry — tokens last ~60 days but can be revoked earlier
Key endpoints
/channels/followers?broadcaster_id={id}&first=100Gets the list of users who follow a channel. Returns full follower details (user_id, user_login, user_name, followed_at) ONLY with moderator:read:followers scope from the broadcaster or a moderator. Without correct scope, returns only total count.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcaster_id | string | required | User ID of the channel (not username) — get via GET /helix/users?login=channelname |
first | number | optional | Max 100 per page (default 20) |
after | string | optional | Pagination cursor from previous response |
Response
1{"data": [{"user_id": "11111", "user_login": "viewer1", "user_name": "Viewer1", "followed_at": "2026-01-10T12:00:00Z"}], "pagination": {"cursor": "abc123"}, "total": 15420}/users?login={username}Gets user information including user_id by username. Use this to convert a channel name to broadcaster_id before calling follower endpoints.
| Parameter | Type | Required | Description |
|---|---|---|---|
login | string | required | Twitch username (lowercase) — can specify multiple: login=user1&login=user2 |
Response
1{"data": [{"id": "12345678", "login": "channelname", "display_name": "ChannelName", "created_at": "2015-06-01T00:00:00Z"}]}/eventsub/subscriptionsCreate an EventSub subscription for channel.follow v2 to receive real-time new-follower events. Requires moderator:read:followers scope. Use webhook transport (App Access Token) for scalable multi-channel monitoring or WebSocket transport (User Access Token) for simple single-channel bots.
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | required | channel.follow (use version 2 — v1 is deprecated) |
version | string | required | Must be 2 for channel.follow |
condition.broadcaster_user_id | string | required | Broadcaster's user ID |
condition.moderator_user_id | string | required | User ID of the moderator authorizing this subscription (can be same as broadcaster) |
transport.method | string | required | webhook or websocket |
transport.callback | string | optional | HTTPS callback URL (webhook transport only) |
transport.secret | string | optional | 10-100 char secret for webhook signature verification |
Request
1{"type": "channel.follow", "version": "2", "condition": {"broadcaster_user_id": "12345678", "moderator_user_id": "12345678"}, "transport": {"method": "webhook", "callback": "https://yourapp.com/twitch/eventsub", "secret": "your_secret_min_10_chars"}}Response
1{"data": [{"id": "sub_id_123", "status": "webhook_callback_verification_pending", "type": "channel.follow", "version": "2"}]}Step-by-step automation
Get the Broadcaster User ID
Why: All follower endpoints use numeric user IDs, not usernames — you must convert the channel name first.
Call GET /helix/users?login=channelname with both the Authorization Bearer token and Client-Id header. The response contains the id field (numeric string) which is the broadcaster_id needed for all follower calls.
1curl -X GET "https://api.twitch.tv/helix/users?login=channelname" \2 -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \3 -H "Client-Id: YOUR_CLIENT_ID"Pro tip: Cache the broadcaster_id — it does not change for the lifetime of the account. No need to look it up on every report run.
Expected result: Numeric string broadcaster_id (e.g., '12345678') needed for all follower API calls.
Fetch the Full Follower List with Pagination
Why: The endpoint returns max 100 followers per page — large channels require multiple requests with cursor pagination.
Call GET /helix/channels/followers with broadcaster_id and first=100. Follow the pagination.cursor value for subsequent pages until no cursor is returned. Each page consumes 1 point from the 800/minute Helix bucket. For a channel with 50,000 followers that's 500 pages — plan your timing accordingly.
1# First page2curl "https://api.twitch.tv/helix/channels/followers?broadcaster_id=12345678&first=100" \3 -H "Authorization: Bearer USER_ACCESS_TOKEN" \4 -H "Client-Id: YOUR_CLIENT_ID"56# Subsequent pages — pass cursor from pagination.cursor7curl "https://api.twitch.tv/helix/channels/followers?broadcaster_id=12345678&first=100&after=CURSOR_VALUE" \8 -H "Authorization: Bearer USER_ACCESS_TOKEN" \9 -H "Client-Id: YOUR_CLIENT_ID"Pro tip: For large channels (100K+ followers), consider only fetching new followers since your last report by sorting by followed_at and stopping when you reach your last known timestamp.
Expected result: Complete list of followers with user_id, user_name, and followed_at timestamps for the channel.
Compare Snapshots and Calculate Growth Metrics
Why: Raw follower data is not a report — you need to calculate new followers, lost followers, and growth rate by comparing to a previous snapshot.
Save follower snapshots to a file or database keyed by user_id. On each report run, compare the current set to the previous set to calculate new followers, unfollows, net change, and percentage growth. Include follower acquisition rate (average new followers per day) for trend analysis.
1# No direct cURL equivalent — this is data processing logic2# After fetching followers, save to file and compare3# Use jq to count total followers from API response:4curl "https://api.twitch.tv/helix/channels/followers?broadcaster_id=12345678&first=1" \5 -H "Authorization: Bearer TOKEN" -H "Client-Id: CLIENT_ID" | jq '.total'Pro tip: For channels with millions of followers, store only new follower timestamps (not full lists) to keep snapshots manageable. Use the followed_at field to identify truly new followers since your last run.
Expected result: A report object with total followers, new followers, unfollows, net change, and average daily acquisition rate.
Deliver the Report to Slack or Discord
Why: A report that lives only in a JSON file provides no value — post it to where the streamer actually looks.
Format the report as a Slack Block Kit message or Discord embed and POST to a webhook URL. Include the most important metrics (net change, new followers count, total, daily rate) at the top, and optionally list up to 10 new followers by name for engagement.
1curl -X POST "https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN" \2 -H "Content-Type: application/json" \3 -d '{4 "embeds": [{5 "title": "Twitch Weekly Follower Report",6 "color": 9520895,7 "fields": [8 {"name": "Total Followers", "value": "15,420", "inline": true},9 {"name": "New This Week", "value": "+342", "inline": true},10 {"name": "Unfollows", "value": "-28", "inline": true}11 ]12 }]13 }'Pro tip: Post weekly reports every Monday at a consistent time. Include the period dates (e.g., 'Jan 8–14') so historical reports are easy to compare.
Expected result: A formatted follower report message appears in your Discord server or Slack channel with all key metrics.
Complete working code
Complete weekly follower report script that fetches the full follower list, compares to the previous snapshot to identify new followers and unfollows, calculates growth metrics, and posts a formatted report to Discord. Handles rate limiting, token validation, and pagination automatically.
1import requests, json, os, time, logging2from datetime import datetime34logging.basicConfig(level=logging.INFO)56CLIENT_ID = os.environ['TWITCH_CLIENT_ID']7ACCESS_TOKEN = os.environ['TWITCH_ACCESS_TOKEN']8DISCORD_WEBHOOK = os.environ.get('DISCORD_WEBHOOK_URL')9CHANNEL_NAME = os.environ['TWITCH_CHANNEL_NAME']10SNAPSHOT_FILE = f'snapshot_{CHANNEL_NAME}.json'1112HEADERS = {'Authorization': f'Bearer {ACCESS_TOKEN}', 'Client-Id': CLIENT_ID}1314def validate_token():15 r = requests.get('https://id.twitch.tv/oauth2/validate', headers={'Authorization': f'OAuth {ACCESS_TOKEN}'})16 if r.status_code != 200:17 raise Exception('Token invalid or expired — re-authorize')18 logging.info('Token valid')1920def get_broadcaster_id(channel_name):21 r = requests.get('https://api.twitch.tv/helix/users', headers=HEADERS, params={'login': channel_name})22 return r.json()['data'][0]['id']2324def get_all_followers(broadcaster_id):25 followers, cursor = [], None26 while True:27 params = {'broadcaster_id': broadcaster_id, 'first': 100}28 if cursor: params['after'] = cursor29 r = requests.get('https://api.twitch.tv/helix/channels/followers', headers=HEADERS, params=params)30 if r.status_code == 429:31 reset = int(r.headers.get('Ratelimit-Reset', time.time() + 60)) - int(time.time())32 time.sleep(max(reset + 1, 1))33 continue34 data = r.json()35 followers.extend(data.get('data', []))36 cursor = data.get('pagination', {}).get('cursor')37 logging.info(f'Fetched {len(followers)}/{data.get("total", 0)}')38 if not cursor: break39 time.sleep(0.1)40 return followers4142def generate_and_send_report(channel_name):43 validate_token()44 broadcaster_id = get_broadcaster_id(channel_name)45 followers = get_all_followers(broadcaster_id)46 current_ids = {f['user_id']: f for f in followers}47 48 previous = {}49 prev_time = datetime.utcnow()50 if os.path.exists(SNAPSHOT_FILE):51 with open(SNAPSHOT_FILE) as f:52 snap = json.load(f)53 previous = snap.get('followers', {})54 prev_time = datetime.fromisoformat(snap.get('timestamp', datetime.utcnow().isoformat()))55 56 new_ids = set(current_ids) - set(previous)57 lost_ids = set(previous) - set(current_ids)58 days = max((datetime.utcnow() - prev_time).total_seconds() / 86400, 1)59 60 with open(SNAPSHOT_FILE, 'w') as f:61 json.dump({'timestamp': datetime.utcnow().isoformat(), 'followers': current_ids}, f)62 63 if DISCORD_WEBHOOK:64 requests.post(DISCORD_WEBHOOK, json={'embeds': [{65 'title': f'Twitch Follower Report — {channel_name}',66 'color': 9520895,67 'fields': [68 {'name': 'Total', 'value': f'{len(current_ids):,}', 'inline': True},69 {'name': 'New', 'value': f'+{len(new_ids)}', 'inline': True},70 {'name': 'Lost', 'value': f'-{len(lost_ids)}', 'inline': True},71 {'name': 'Net', 'value': str(len(new_ids) - len(lost_ids)), 'inline': True},72 {'name': 'Daily Rate', 'value': f'{len(new_ids)/days:.1f}/day', 'inline': True},73 ]74 }]})75 logging.info('Report delivered to Discord')7677generate_and_send_report(CHANNEL_NAME)Error handling
Must provide a valid Client-ID or OAuth tokenEither the Authorization or Client-Id header is missing or invalid. Twitch requires BOTH headers on every Helix request — forgetting either causes 401.
Ensure both headers are present: Authorization: Bearer <access_token> and Client-Id: <client_id>. Validate your token via GET https://id.twitch.tv/oauth2/validate before making requests.
Refresh the access token using the refresh token, then retry with the new token.
Token verification failedThe access token has expired (~60 days) or was revoked by the user. User tokens can be revoked at any time at www.twitch.tv/settings/connections.
Use the refresh token to get a new access token via POST to https://id.twitch.tv/oauth2/token with grant_type=refresh_token. If the refresh token is also invalid, re-authorize the user.
Refresh token once and retry. If refresh fails, trigger re-authorization flow.
Missing required scopeThe access token lacks the moderator:read:followers scope. Without this scope, the endpoint returns only total follower count, not individual follower data.
Re-authorize the user with the correct scope: moderator:read:followers. The user must be the broadcaster or a moderator on the channel. Redirect to the authorization URL with the required scope.
No retry — fix the authorization flow to include the correct scope.
Rate limit exceededThe 800 points/minute Helix bucket has been exhausted. Paginating large follower lists (one request per page) can hit this limit on very large channels.
Implement exponential backoff: check the Ratelimit-Reset header for when the bucket resets, then wait that long before retrying.
Wait until Ratelimit-Reset timestamp (Unix epoch) plus 1 second, then retry.
Resource not foundThe broadcaster_id does not match an existing Twitch user, or the user account was deleted/banned.
Verify the broadcaster_id using GET /helix/users?id=12345678. If the channel was renamed, use GET /helix/users?login=newname to get the updated user_id.
No retry — fix the broadcaster_id lookup.
Rate Limits for Twitch Helix API
| Scope | Limit | Window |
|---|---|---|
| Per access token | 800 points | per minute (token bucket algorithm) |
| Per page of followers | 1 point | per GET /channels/followers request |
| Large channel pagination | 500 requests for 50K followers | spans multiple minutes due to 800/min limit |
1import time, requests23def helix_get_with_retry(url, headers, params, max_retries=5):4 for attempt in range(max_retries):5 r = requests.get(url, headers=headers, params=params)6 if r.status_code == 429:7 reset = int(r.headers.get('Ratelimit-Reset', time.time() + 60))8 wait = max(reset - int(time.time()) + 1, 1)9 print(f'Rate limited, waiting {wait}s')10 time.sleep(wait)11 continue12 if r.status_code == 401:13 raise Exception('Token expired — refresh needed')14 r.raise_for_status()15 return r.json()16 raise Exception('Max retries exceeded')- Add a 100ms delay between pagination requests to avoid hitting the 800/min bucket
- For large channels, run follower reports weekly rather than daily — full list pagination is expensive
- Cache the broadcaster_id — it never changes and saves a request on every run
- Validate the user token at the start of every run to catch revocations before batch processing
- Check Ratelimit-Remaining header to dynamically throttle if approaching the limit
Security checklist
- Store Client ID, Client Secret, and access tokens in environment variables — never hardcode
- Validate user tokens hourly via GET https://id.twitch.tv/oauth2/validate — users can revoke without notice
- Use HTTPS for your OAuth redirect URI — HTTP redirects are rejected by Twitch
- For EventSub webhooks, use a 10-100 character secret and verify HMAC-SHA256 signatures on all incoming requests
- Refresh access tokens proactively before expiry (~55 days) to avoid interruption
- Limit the OAuth scope to only moderator:read:followers — do not request unnecessary permissions
- Log token validation failures and alert immediately — silent token revocation causes missed reports
Automation use cases
Weekly Growth Dashboard
intermediateEvery Monday morning, fetch the full follower list, compare to last week, and post a formatted report to Discord with net change, top new followers, and daily acquisition rate.
Real-Time New Follower Alerts
intermediateSubscribe to channel.follow EventSub v2 and post an instant alert to Discord or stream overlay every time a new follow happens.
Milestone Notifications
beginnerPoll total follower count every hour and send a special celebration message when crossing milestone counts (1K, 5K, 10K, etc.).
Follower Churn Analysis
advancedTrack follower-to-unfollow ratios over time and alert when churn rate exceeds a threshold, helping identify content quality issues.
No-code alternatives
Don't want to write code? These platforms can automate the same workflows visually.
Zapier
Free tier (100 tasks/month); paid from $20/monthZapier's Twitch integration can trigger on new followers and post to Discord, Slack, or Google Sheets — no code required, though it does not support full follower list exports.
- + No code required
- + Instant new-follower triggers
- + Connects to 7,000+ apps
- - Cannot export full follower lists
- - Polling-based (not real-time)
- - Costs $20+/month
Make (formerly Integromat)
Free tier (1,000 operations/month); paid from $9/monthMake supports Twitch triggers and can post follower milestone alerts to multiple platforms with conditional logic and data transformation.
- + More flexible than Zapier
- + Better for complex report formatting
- + Lower cost
- - Limited Twitch coverage
- - No full follower list support
n8n
Free self-hosted; Cloud from $20/monthn8n's Twitch node supports basic follower event monitoring and can chain into Discord or Slack notifications with a self-hosted free option.
- + Self-hosted = free
- + Full workflow control
- + Active community Twitch integrations
- - Limited Twitch endpoint coverage
- - Requires self-hosting setup
Best practices
- Always include both Authorization: Bearer and Client-Id headers — omitting either returns 401
- Validate user tokens hourly — users can revoke access silently, causing silent report failures
- Use EventSub channel.follow v2 for real-time alerts instead of polling GET /channels/followers
- For large channels, store only the delta (new/lost followers) rather than full snapshots to keep storage manageable
- Use the total field in the response as a progress indicator during pagination rather than counting items
- Request quota increase via dev.twitch.tv if you regularly hit the 800 points/minute limit with multiple channels
- Never use the deprecated GET /users/follows endpoint — it is completely removed and returns 404
Ask AI to help
Copy one of these prompts to get a personalized, working implementation.
I'm building a Twitch follower report automation in Python using the Helix API. I have a user access token with moderator:read:followers scope from the broadcaster. When I call GET /helix/channels/followers I get a 401 error even though I'm including the Authorization header. Here's my code: [paste code]. What's missing?
Build a Twitch Channel Growth Dashboard that shows follower count history, weekly new/lost follower metrics, and milestone celebrations. Pull data from the Twitch Helix API using OAuth 2.0 (moderator:read:followers scope) and display a line chart of follower growth over time. Store historical data in Supabase. Show a Discord notification button to manually post the latest report.
Frequently asked questions
Is the Twitch API free?
Yes, the Twitch Helix API is free with no monthly cost. You need a free developer account at dev.twitch.tv to register an app and get credentials. There are no paid API tiers — rate limits (800 points/minute) apply equally to all developers.
Why does GET /channels/followers only return the total count and not the actual list?
Full follower list access requires the moderator:read:followers scope from the broadcaster or one of their moderators. Without this scope, the endpoint intentionally returns only the total count. The restriction was enforced September 12, 2023 to protect follower privacy. Re-authorize the user with the correct scope to get the full list.
What happened to GET /users/follows?
GET /users/follows is completely removed. It no longer exists in the Helix API. Any code using this endpoint will get a 404. Use GET /channels/followers (broadcaster perspective) or GET /channels/following (user perspective) instead.
What happens when I hit the rate limit?
You receive HTTP 429. Check the Ratelimit-Reset header which contains a Unix timestamp for when your 800-point bucket resets (typically within 60 seconds). Wait until that time plus 1 second, then retry. Implement exponential backoff for repeated 429s.
Can I get real-time new-follower notifications instead of polling?
Yes — subscribe to EventSub event type channel.follow version 2 with the moderator:read:followers scope. This is the recommended approach. PubSub (the old real-time system) was fully decommissioned on April 14, 2025 and can no longer be used.
Do App Access Tokens work for follower reports?
App Access Tokens (from Client Credentials flow) return only the total follower count, not the individual follower list. For the full list, you must use a User Access Token from the broadcaster or a moderator with the moderator:read:followers scope.
Can RapidDev help build a Twitch analytics dashboard?
Yes — RapidDev has built 600+ integrations including Twitch automation tools and analytics dashboards. We can build a complete follower tracking system with EventSub real-time alerts, weekly reports, and milestone notifications. Book a free consultation at rapidevelopers.com.
How do I track follower growth without paginating millions of records?
Use EventSub channel.follow v2 for real-time new-follower events and only store deltas. For the total count, check the total field in a single GET /channels/followers?first=1 request (1 quota point) rather than paginating the entire list.
Need this automated?
Our team has built 600+ apps with API automations. We can build this for you.
Book a free consultation