Interview Prep · Lesson 06
How leading APIs do it
Naming what a real company does — "Stripe uses a token bucket in Redis," "GitHub versions with a dated header" — turns a generic answer into a credible one. This page collects how Stripe, AWS, GitHub, HubSpot and Shopify actually implement the patterns this course teaches, so you can cite real systems on the whiteboard. Exact numbers drift; always confirm against current docs (linked at the bottom).
By the end you'll be able to
- Cite concrete production examples for rate limiting, versioning, auth, gateways, idempotency, webhooks and pagination.
- Explain Stripe's token-bucket rate limiter and dated-version model in detail.
- Contrast the design choices reputable APIs made and why.
Rate limiting
Almost every public API limits you, but the algorithm and the headers differ. Note the split: token/leaky bucket (smooth, allows controlled bursts) vs fixed windows, and whether limits are per-account, per-IP, or per-cost.
| Company | Algorithm | Limit (typical) | Signals to the client |
|---|---|---|---|
| Stripe | Token bucket (in Redis) | ~100 read & ~100 write req/s (live) | 429 + Retry-After |
| Shopify | Leaky bucket (REST); calculated query cost (GraphQL) | e.g. 40-req bucket, leaks ~2/s (standard REST) | 429 + Retry-After; X-Shopify-Shop-Api-Call-Limit |
| GitHub | Fixed hourly quota + secondary abuse limits | 5,000 req/hour (authenticated) | X-RateLimit-Limit/Remaining/Reset/Used; 403/429 |
| AWS API Gateway | Token bucket (steady rate + burst) | Configurable per stage/method + usage-plan quotas | 429 Too Many Requests |
| HubSpot | Rolling window, per account | ~100–190 req / 10 s (varies by tier) + daily cap | 429; X-HubSpot-RateLimit-* |
Stripe publicly described running four cooperating limiters, not one — a great thing to cite when asked "how would you protect an API under load?":
- Request rate limiter — a token bucket per account (implemented in Redis) capping requests/second; the everyday limit that returns
429. - Concurrent request limiter — caps how many requests one account can have in flight at once, protecting against a few slow expensive calls hogging workers.
- Fleet usage load shedder — reserves a slice of total capacity for critical request types (e.g. creating a charge) so non-critical traffic can't starve the money path during a surge.
- Worker utilization load shedder — when workers are nearly saturated, sheds the least-important traffic first to keep the system alive.
The lesson: at scale, "rate limiting" becomes layered — a fairness limiter plus load shedders that protect the most important work. (See the rate-limiting lesson for the algorithms.)
API versioning
| Company | Scheme | How it's selected |
|---|---|---|
| Stripe | Date-based (e.g. 2024-06-20) | Each account pinned to the version it onboarded on; override per request with the Stripe-Version header. Backward compatibility held for years. |
| GitHub | Date-based | X-GitHub-Api-Version header (e.g. 2022-11-28); announced sunsets. |
| AWS | Date-based service versions | e.g. EC2 Version=2016-11-15 request parameter; SDKs pin it. |
| HubSpot | URL path | /crm/v3/... — major version in the path; documented deprecation dates. |
| Azure | Date-based query param | ?api-version=2023-07-01. |
The standout is Stripe's pinning model: when you first call the API, your account is locked to the current dated version. New dated versions ship constantly, but your integration keeps seeing the exact shapes it was built against until you opt to upgrade (and read the changelog of what changed between dates). It's the cleanest answer to "how do you evolve without breaking anyone" — additive changes go out freely; anything breaking is gated behind a new date. (See versioning and evolving APIs.)
Authentication
| Company | Mechanism | Notable detail |
|---|---|---|
| Stripe | API keys (Bearer) | Secret (sk_live_…) vs publishable (pk_…); restricted keys with per-resource scopes; separate test/live keys. |
| AWS | Signature V4 request signing | Client signs each request: build a canonical request, derive a signing key via an HMAC chain (date → region → service → aws4_request), send the signature in Authorization with X-Amz-Date. The secret never travels. |
| GitHub | PAT, OAuth App, GitHub App | A GitHub App signs a short-lived JWT with its private key, then exchanges it for an installation access token scoped to one install. |
| HubSpot | OAuth 2.0 + private-app tokens | Legacy API keys were deprecated (2022) in favour of OAuth and private-app access tokens. |
| OAuth 2.0 + OIDC | Access tokens for authZ, ID tokens (JWT) for authN. |
The contrast worth naming: Stripe's bearer keys are dead-simple (great DX) but the secret is sent on every call (TLS-protected); AWS SigV4 never transmits the secret and signs the request body for integrity, at the cost of complexity. Different points on the simplicity↔security curve. (See keys & JWTs and OAuth.)
API gateways
| System | Type | What it handles |
|---|---|---|
| AWS API Gateway | Managed | Routing, throttling (token bucket), usage plans + API keys, authorizers (Lambda/Cognito/JWT), request/response mapping, stages + canary, caching, WAF. |
| Kong | Self-hosted (on NGINX) | Plugin model: auth, rate limiting, transformations, logging. |
| Envoy | Proxy / mesh data plane | L7 routing, retries, circuit breaking, observability; core of many service meshes. |
| Netflix Zuul / Spring Cloud Gateway | Self-hosted | Edge routing, auth, and cross-cutting filters at massive scale. |
The pattern across all of them: the gateway is where the cross-cutting concerns live — auth, rate limiting, TLS termination, routing — so individual services don't each reimplement them. (See the API gateway deep dive.)
Idempotency
The money-handling APIs converged on the same design: a client-supplied idempotency key the server uses to dedupe retries.
| Company | Header | Behaviour |
|---|---|---|
| Stripe | Idempotency-Key | Client sends a unique key (e.g. a UUID) on a POST; Stripe stores the result and replays the same response for ~24 hours. |
| PayPal | PayPal-Request-Id | Same idea for safely retrying payment calls. |
| Adyen | Idempotency-Key | Dedupes duplicate payment submissions. |
(See idempotency and the payments case study.)
Webhook signatures
| Company | Header | Scheme |
|---|---|---|
| Stripe | Stripe-Signature | HMAC-SHA256 over timestamp + "." + payload with the endpoint's signing secret; includes a t= timestamp checked against a tolerance window to block replays. Failed deliveries are retried with backoff. |
| GitHub | X-Hub-Signature-256 | HMAC-SHA256 over the raw body with the webhook secret. |
| HubSpot | X-HubSpot-Signature (v3) | HMAC-SHA256 over method + URI + body + timestamp. |
The universal rule: verify the signature on the raw body before trusting a webhook, and make handlers idempotent because all of them retry. (See designing webhooks and debugging webhooks.)
Pagination
| Company | Style | Mechanism |
|---|---|---|
| Stripe | Cursor | limit + starting_after/ending_before (an object id as the cursor); response has has_more. |
| GitHub | Page + Link header | ?page=&per_page= with an RFC-5988 Link header carrying rel="next"/"prev"/"last". |
| Slack | Cursor | cursor + response_metadata.next_cursor. |
Note the trend: high-write APIs (Stripe, Slack) prefer cursors because offset pagination drifts and slows on large, changing datasets. (See pagination.)
Drop one concrete reference per decision: "I'd rate-limit with a token bucket — the way Stripe does, per-account in Redis, returning 429 with Retry-After." "I'd version like Stripe: date-based, pin accounts, additive-only between dates." It signals you've read real systems, not just theory. Don't over-claim exact numbers — say "around" and note they change.
Citing a company incorrectly is worse than not citing one. Don't say "AWS uses JWTs for everything" (it's SigV4 request signing) or "Stripe uses URL versioning" (it's dated header/pinning). If you're unsure of the specifics, describe the pattern ("a token-bucket limiter returning 429") rather than misattributing it.
🧠 Quick check
1. Stripe's everyday rate limiter is built on which algorithm?
Stripe described a per-account token bucket implemented in Redis as its request rate limiter, alongside a concurrency limiter and two load shedders. It returns 429 with Retry-After.
2. How does Stripe stop a new API version from breaking existing integrations?
Stripe uses date-based versions and pins each account to the version it started on. Additive changes ship freely; breaking changes are gated behind a new dated version the integrator chooses to adopt.
3. AWS Signature V4 differs from a Stripe bearer key mainly because:
SigV4 signs each request with a key derived from the secret via an HMAC chain, so the secret itself never goes over the wire and the request body is integrity-protected — more complex than a bearer key, but stronger.
Key takeaways
- Rate limiting: Stripe = token bucket + load shedders; Shopify = leaky bucket / GraphQL cost; GitHub = hourly quota + secondary limits; AWS API Gateway = token-bucket throttling.
- Versioning: Stripe/GitHub/AWS = dated; Stripe pins accounts; HubSpot = URL path.
- Auth: Stripe = bearer keys (+ restricted); AWS = SigV4 request signing; GitHub Apps = JWT → installation token; HubSpot = OAuth/private-app.
- Idempotency / webhooks: client
Idempotency-Key(Stripe ~24h); webhooks are HMAC-signed and retried — verify + dedupe. - Pagination: high-write APIs prefer cursors (Stripe, Slack); GitHub uses page + Link header.
Sources & further reading
- Stripe — Scaling your API with rate limiters · Rate limits · Versioning · Idempotency · Webhook signatures
- AWS — API Gateway throttling · Signature Version 4
- GitHub — REST rate limits · API versions · Webhook validation
- HubSpot — Usage & rate limits · Webhook validation
- Shopify — API rate limits (leaky bucket / query cost)