Fintech API’lerinde Rate Limiting: Case Study ile Tasarım Prensipleri (Auth, Payments, Webhook)
Case study üzerinden Auth, Payments ve Webhook akışları için rate limiting tasarım prensipleri.
Bu yazıda amaç, belirli bir projeyi “birebir reçete” gibi kopyalamak değil; bir fintech API’de rate limiting kararlarını nasıl modelleyeceğimizi teknik olarak netleştirmektir.
Kapsam:
- Auth akışları (login, token yenileme, şifre sıfırlama)
- Payment akışları (ödeme başlatma / oturum oluşturma)
- Webhook akışları (sağlayıcı callback trafiği)
1) Neden tek bir limit yaklaşımı yerine akış bazlı yaklaşım?
Fintech sistemlerinde her endpoint aynı risk ve maliyet profilini taşımaz:
- Auth: parola deneme saldırıları (brute-force), kimlik bilgisi doldurma (credential stuffing) ve hesap ele geçirme riski taşır.
- Payments: doğrudan finansal etkisi olan write işlemleri; yanlış limit müşteri kaybı veya işlem başarısızlığına gider.
- Webhook: üçüncü parti retry davranışı içerir; agresif throttling entegrasyon stabilitesini bozabilir.
Bu nedenle ilk prensip:
Rate limiting’i “trafik azaltma” problemi değil, risk ve iş etkisi segmentasyonu problemi olarak ele alın.
2) Endpoint grupları
Tipik bir fintech API’sinde trafiği üç ana grupta düşünebiliriz:
- Auth tarafı (
/api/v1/auth/...)POST /login,POST /refresh-token,POST /forgot-password,POST /reset-password
- Payments tarafı (
/api/payments/...)POST /bill,POST /create-session,POST /create-setup-session
- Webhook tarafı
POST /api/payments/webhook
Bu ayrım ilk bakışta basit görünebilir; ancak operasyonel olarak ciddi fark yaratır. 429 artışlarının güvenlik kaynaklı mı yoksa kapasite kaynaklı mı olduğunu ancak bu gruplar üzerinden net okuyabiliriz.
3) Partition key (limit anahtarı) tasarımı
Yalnızca IP adresine dayalı bir limit yaklaşımı, production’da sıkça yanlış pozitif engellemelere yol açar.
Daha sağlıklı bir tasarımda partition key, akışın riskini ve iş bağlamını birlikte yansıtmalıdır:
- Auth:
source_ip + user_identifier(hash) - Payments:
merchant_id/tenant_id + endpoint - Webhook:
provider + source
Buradaki amaç, yalnızca trafiğin geldiği yeri değil; işlemin kime ve hangi bağlamda ait olduğunu da anahtara dahil etmektir.
4) .NET 8 RateLimiter API ile kısa, gerçekçi örnek
Bu örnek yalnızca uygulama içi limit ayrımını gösterir; dağıtık sistemlerde veya çoklu instance ortamında sayaçların ortak bir datastore’da tutulması konusuna girmez.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.OnRejected = async (ctx, token) =>
{
var retryAfterSeconds = 30; // prod ortamda dinamik hesaplanmalı
ctx.HttpContext.Response.Headers["Retry-After"] = retryAfterSeconds.ToString();
await ctx.HttpContext.Response.WriteAsJsonAsync(new
{
code = "rate_limit_exceeded",
message = "Rate limit exceeded",
retryAfterSeconds,
traceId = ctx.HttpContext.TraceIdentifier
}, cancellationToken: token);
};
options.AddPolicy("AuthSensitive", httpContext =>
RateLimitPartition.GetSlidingWindowLimiter(
partitionKey: $"auth:{httpContext.Connection.RemoteIpAddress}:{httpContext.Request.Headers["X-User-Key"]}",
factory: _ => new SlidingWindowRateLimiterOptions
{
PermitLimit = 20,
Window = TimeSpan.FromMinutes(10),
SegmentsPerWindow = 10,
QueueLimit = 0,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst
}));
options.AddPolicy("Payments", httpContext =>
RateLimitPartition.GetTokenBucketLimiter(
partitionKey: $"pay:{httpContext.Request.Headers["X-Merchant-Id"]}:{httpContext.Request.Path}",
factory: _ => new TokenBucketRateLimiterOptions
{
TokenLimit = 60,
TokensPerPeriod = 20,
ReplenishmentPeriod = TimeSpan.FromSeconds(1),
AutoReplenishment = true,
QueueLimit = 0,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst
}));
options.AddPolicy("Webhook", httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: $"wh:{httpContext.Connection.RemoteIpAddress}",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 120,
Window = TimeSpan.FromMinutes(1),
QueueLimit = 0,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst
}));
});
Notlar:
X-Merchant-Id/X-User-Keygibi değerler, production’da trusted context’ten gelmelidir.- 429 yanıtında body +
Retry-Afterbirlikte verilmelidir.
5) Distributed rate limiting: mimari seviyede nasıl düşünülmeli?
Uygulama tek instance değilse, sadece process-memory sayaçlarıyla limit doğruluğu korunamaz.
Mimari seviyede yaklaşım:
- Ortak state kullanan bir distributed limiter katmanı planla.
- Uygulama içi limiter, hızlı bir koruma katmanı olarak konumlandırılabilir.
- Gerekirse gateway seviyesinde ek koruma uygulayın.
Buradaki hedef, tek bir teknoloji detayı değil; şu dengeyi kurmaktır:
- Düşük gecikme
- Tutarlı limit davranışı
- Operasyonel yönetilebilirlik
6) Idempotency + rate limiting: payment güvenliğinin iki ayağı
Payment write endpoint’lerinde şu yanlış varsayım sık görülür:
“Rate limiting varsa duplicate charge riski düşüktür.”
Bu doğru değildir.
- Rate limiting: aşırı veya kötüye kullanım amaçlı trafiği sınırlar.
- Idempotency: aynı işlemin finansal olarak tekrar yazılmasını engeller.
Bu yüzden:
Idempotency-Keyödeme yazma uçlarında zorunlu olmalı.- 429 sonrası retry aynı idempotency anahtarıyla yapılmalı.
- Sistem aynı anahtar için deterministic response döndürmeli.
Sonuç
Rate limiting’i yalnızca “kaç istek/dakika?” sorusuna indirgemek yeterli değildir. Risk seviyesi, algoritma tercihi, idempotency ve dağıtık yapı birlikte değerlendirilmelidir.
Doğru kurgu, hem güvenliği güçlendirir hem de ödeme akışında gereksiz kesintileri azaltır.
