Harvester pulls data from external networks, and what needs protecting is the ingestion channel itself — two vectors: verifying incoming webhooks and throttling the rate of calls to third-party APIs. Narrowly about the channel: encryption of credentials is held by sources, and carrying access rights over from the source by acl-identity.
if per provider: the core holds a
single verification mechanism, and the
connector
brings its own pluggable verifier — it declares the
signature scheme, how to extract the dedup key, and whether the
delivery carries a timestamp. The core runs three steps in a
strict order — freshness → signature → dedup
(cheap → expensive): first filter by timestamp and headers, and
only what passes the signature is let into dedup — otherwise an
inauthentic stream would itself become a DoS on the dedup store.
A new source = a new verifier; the core stays untouched.
SET key NX EX — the first time sets it, a repeat
sees the key and is discarded.
The TTL depends on the timestamp: 15 minutes
where there is a timestamp (freshness has already cut off the
old), and 24 hours where there is none — to catch manual
redeliveries too. The channel itself is TLS-only (terminated at
the reverse proxy).
v0:ts:body, timestamp present (5-min
window), dedup by event_id.
X-Hub-Signature-256, no timestamp, dedup by
X-GitHub-Delivery.
Idempotency-Key / Event-UUID.
X-Hub-Signature (admin) or JWT (Connect),
no timestamp, dedup by
X-Atlassian-Webhook-Identifier.
tier) caps. So the limiter key is
built from rate_limit_scope in the
connector manifest
(tenant / account_token / workspace_method / site), not from an
abstract “source” — there is no single figure across them all.
The manifest also declares the starting and maximum rate: a safe
starting point for a typical tier, while the runtime feels out
the real boundary from the source's own responses.
429 it drops
sharply ×0.5 (additive-increase / multiplicative-decrease). The
floor and start are the rate from the manifest, the cap is its
maximum. The current rate is not reset between
runs — it is the API's learned capacity; in Redis it lives with
a TTL of hours. The executor is a distributed token bucket in
Redis, with atomic (Lua) charging, a per-scope key, surviving a
worker restart.
Retry-After is a hard pause on the entire
scope, respected by all workers, and it overrides AIMD. And if
X-RateLimit-Remaining is low — we slow down ahead of
time: safe_rps = Remaining / (Reset − now), taking
the min with the AIMD rate.