Event Ingestion
How Syntropic137 receives GitHub events through webhooks and polling.
Syntropic137 uses a hybrid approach to receive GitHub events: webhooks for real-time delivery, and Events API polling as a zero-config fallback. Both sources feed into a unified pipeline with content-based deduplication — the same event is never processed twice, regardless of how it arrives.
How It Works
- Webhook endpoint receives GitHub webhook deliveries in real time
- Events API poller runs as a background task, polling GitHub's Events API for repositories that have active triggers
- Both sources normalize their payloads into a common
NormalizedEventformat - The EventPipeline checks each event against a dedup store (Redis) using content-based keys — if the event was already processed, it's skipped
- New events are routed to trigger evaluation, which fires matching workflows
Polling Modes
The poller adapts its behavior based on whether webhooks are arriving:
| Mode | Interval | When it activates |
|---|---|---|
| Active Polling | 60 seconds | No webhook received in 30 minutes (or never) |
| Safety Net | 300 seconds | Webhooks arriving normally |
When you first start Syntropic137 without a webhook URL configured, the poller operates in Active Polling mode — checking for new events every 60 seconds. Once webhooks start arriving (after configuring a tunnel or public URL), the poller automatically backs off to Safety Net mode, polling every 5 minutes as a catch-up mechanism.
If webhooks stop arriving (tunnel goes down, GitHub outage), the poller detects the gap after 30 minutes and switches back to active polling.
Deduplication
When both webhooks and polling are active, the same event may arrive from both sources. Syntropic137 prevents double-processing using content-based dedup keys.
Each event type has a dedicated key extractor that uses stable identifiers present in both webhook and Events API payloads:
- Push events: repository + commit SHA (
afterfield) - Pull requests: repository + PR number + action +
updated_attimestamp - Check runs: repository + check run ID + action
- Issue comments: repository + comment ID + action
Keys are stored in Redis with a 24-hour TTL using atomic SETNX — the first
source to deliver an event "wins," and the duplicate from the other source is
silently skipped.
Fail-open behavior
If Redis is temporarily unavailable, events are processed anyway rather than dropped. Trigger safety guards (fire counts, cooldown periods) provide second-layer protection against duplicate workflows.
GitHub API Quota
The Events API has a rate limit of 60 conditional requests per hour per installation. Syntropic137 minimizes quota usage through several mechanisms:
- ETag caching: Uses
If-None-Matchheaders for conditional requests. 304 Not Modified responses still count against the limit but return no data. - Selective polling: Only repositories with active triggers are polled. Repos without triggers consume zero API calls.
- Adaptive intervals: In Safety Net mode (webhooks healthy), polling drops to every 5 minutes — roughly 12 requests per hour per repo.
- X-Poll-Interval: The poller respects GitHub's recommended minimum interval
from the
X-Poll-Intervalresponse header.
Quota estimates
| Mode | Polls/hour/repo | With 5 repos |
|---|---|---|
| Active Polling (60s) | ~60 | ~300 |
| Safety Net (300s) | ~12 | ~60 |
Configuration
All polling settings use the SYN_POLLING_ environment variable prefix:
| Variable | Default | Description |
|---|---|---|
SYN_POLLING_DISABLED | false | Disable polling entirely |
SYN_POLLING_POLL_INTERVAL_SECONDS | 60.0 | Active polling interval (min: 10s) |
SYN_POLLING_SAFETY_NET_INTERVAL_SECONDS | 300.0 | Safety net interval (min: 60s) |
SYN_POLLING_WEBHOOK_STALE_THRESHOLD_SECONDS | 1800.0 | Seconds without webhook before active polling (min: 60s) |
SYN_POLLING_DEDUP_TTL_SECONDS | 86400 | Redis dedup key TTL (min: 3600s) |
Disabling Polling
If you have reliable webhook delivery and want zero Events API quota usage:
SYN_POLLING_DISABLED=trueThis is recommended for production deployments with a stable public webhook URL or Cloudflare Tunnel. The webhook endpoint continues to work normally regardless of this setting.
Syntropic137 Docs v0.18.0 · Last updated March 2026