Event Ingestion
How Syntropic137 receives GitHub events through webhooks, Events API polling, and Checks API polling.
Syntropic137 uses a hybrid approach to receive GitHub events from three sources: webhooks for real-time delivery, Events API polling for zero-config event ingestion, and Checks API polling for CI failure detection (self-healing). All three 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 (~1s)
- Events API poller runs as a background task, polling GitHub's Events API for repositories that have active triggers (17 event types including PR, push, etc.)
- Checks API poller watches commit SHAs from PR events and polls
GET /repos/{owner}/{repo}/commits/{sha}/check-runsto detect CI failures, enabling self-healing without webhooks (~30-90s latency) - All three 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.
Checks API Polling (Self-Healing)
The Checks API poller enables zero-config self-healing: detecting CI failures without requiring a webhook URL or tunnel setup.
How it works:
- When a
pull_requestevent arrives (from either the Events API poller or webhooks), the pipeline registers the PR's head commit SHA - The Checks API poller periodically calls
GET /repos/{owner}/{repo}/commits/{sha}/check-runsfor each registered SHA - When a check run completes with
conclusion: failure, the poller synthesizes acheck_run.completedevent identical to what a webhook would deliver - The synthesized event flows through the same
EventPipeline.ingest()path, triggering self-healing workflows
The poller only runs when at least one active trigger listens for check_run
events. SHAs are automatically cleaned up after all check runs complete or after
a 2-hour TTL (for abandoned PRs).
| Mode | Interval | When it activates |
|---|---|---|
| Active Polling | 30 seconds | No webhook received in 30 minutes |
| Safety Net | 120 seconds | Webhooks arriving normally |
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) |
SYN_POLLING_CHECK_RUN_POLL_INTERVAL_SECONDS | 30.0 | Checks API active polling interval (min: 10s) |
SYN_POLLING_CHECK_RUN_SAFETY_INTERVAL_SECONDS | 120.0 | Checks API safety net interval (min: 30s) |
SYN_POLLING_CHECK_RUN_SHA_TTL_SECONDS | 7200 | Max age for pending SHAs before cleanup |
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.25.4 · Last updated March 2026