# 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
GitHub Webhook Events API Poller
NormalizedEvent Dedup (Redis) Trigger Evaluation
1. **Webhook endpoint** receives GitHub webhook deliveries in real time 2. **Events API poller** runs as a background task, polling GitHub's Events API for repositories that have active triggers 3. Both sources normalize their payloads into a common `NormalizedEvent` format 4. The **EventPipeline** checks each event against a dedup store (Redis) using content-based keys — if the event was already processed, it's skipped 5. 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 (`after` field) - **Pull requests:** repository + PR number + action + `updated_at` timestamp - **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-Match` headers 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-Interval` response 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: ```bash SYN_POLLING_DISABLED=true ``` This 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.