Skip to content

feat(faceit): add webhook-triggered synchronization - #19

Draft
gouveags wants to merge 1 commit into
mainfrom
feat/faceit-webhook-sync
Draft

feat(faceit): add webhook-triggered synchronization#19
gouveags wants to merge 1 commit into
mainfrom
feat/faceit-webhook-sync

Conversation

@gouveags

Copy link
Copy Markdown
Collaborator

Summary

Automatic FACEIT updates currently depend only on periodic polling, which either wastes requests or leaves long stale windows. This proposal adds an authenticated webhook wake-up path while keeping the existing database-backed worker as the sole owner of canonical FACEIT synchronization and retaining scheduled reconciliation as a fallback.

This PR is intentionally a draft. Before it becomes merge-ready, a real FACEIT App Studio delivery must be captured, redacted, and added as a fixture to confirm that its championship identifier matches the Data API championship ID used by Ace.

Problem

The current scheduler updates active championships frequently even when no match is live, while a lower fixed frequency could miss match transitions. FACEIT webhooks can accelerate meaningful changes, but accepting external payloads directly would create authentication, payload-trust, duplicate-delivery, concurrency, and observability risks.

Changes

src/app/api/webhooks/faceit/route.ts and src/lib/faceit-webhook.ts

  • Add a fail-closed POST /api/webhooks/faceit callback protected by a minimum-32-byte shared header secret and fixed-length timing-safe digest comparison.
  • Enforce JSON content type, a streamed 64 KiB body limit, an exact event allowlist, UUID validation, and bounded logging.
  • Treat the webhook only as a wake signal; it never persists teams, matches, scores, or results and never calls FACEIT inline.
  • Wake only an exact linked championship, preserve an already-earlier due time, record linked disabled/terminal receipts without scheduling them, and return 204 for unlinked or unmappable events.
  • Use a monotonic webhook generation plus compare-and-swap updates so reordered, duplicate, same-millisecond, and in-flight deliveries cannot lose pending work.

src/lib/faceit-championship-sync.ts

  • Replace fixed 2/15-minute polling with a 24-hour watchdog, exact wake-up 30 minutes before the next known start, 5-minute live/imminent polling, and a six-hour overdue bound for stale scheduled matches.
  • Perform one final reconciliation one hour after a terminal state, then stop successful terminal polling while retaining bounded retry behavior for failures.
  • Preserve webhook work that arrives during an in-flight FACEIT fetch by comparing the persisted webhook generation captured before the fetch.

Prisma and admin UI

  • Persist webhook generation, last accepted webhook time, and last accepted event through a nullable/backward-compatible migration.
  • Display the last accepted webhook in /admin/faceit alongside the existing automatic synchronization health state.

Configuration and operations

  • Document FACEIT_WEBHOOK_SECRET, the callback/header configuration, the narrow Organizer event list, verification steps, and the real-payload fixture gate.
  • Keep the current systemd timer, worker lease, retry backoff, snapshots, manual synchronization, and deployment topology.

How to test

  1. Run npm run db:generate, npm run db:migrate, and npm run db:migrate:status against a clean SQLite database.
  2. Run npm run test:faceit-webhook and verify authentication, body limits, payload isolation, linked-record isolation, disabled/terminal behavior, reordered deliveries, and adaptive scheduling pass.
  3. Run npm run test:faceit-sync and verify a webhook arriving during a FACEIT fetch remains immediately due after that fetch commits.
  4. Run npm run check, npm audit --audit-level=high, and npm run build.
  5. Before removing draft status, configure a temporary App Studio Organizer subscription, capture and redact a real event, verify its championship ID mapping, add it as a fixture, and confirm the callback returns 202 and updates Último webhook.

Deployment note

Do not configure the FACEIT App Studio subscription yet. If this proposal is eventually approved, deploy the code and migration first, set a freshly generated FACEIT_WEBHOOK_SECRET on the server, restart the application, and only then configure the callback and shared header in App Studio. Scheduled reconciliation continues working if the secret or subscription is absent.

Draft blockers

  • Capture and redact a real FACEIT App Studio match event.
  • Confirm payload.entity.id is the same championship UUID used by /data/v4/championships/{id}.
  • Replace or supplement the synthetic envelope test with that fixture before marking ready for review.

@gouveags

Copy link
Copy Markdown
Collaborator Author

@greptileai review

@gouveags

Copy link
Copy Markdown
Collaborator Author

CI follow-up:

  • Validar aplicação passed.
  • The publish job skipped, as expected for this draft PR.
  • Lighthouse mobile (99+) completed but failed the repository-wide 99-point public-page gate: / 96, /copa-ace-10 97, /news 97, /inscreva-se 98.
  • This is not attributable to the webhook proposal: the immediately preceding PR run already failed the same gate on / 97, /copa-ace-10 98, and /inscreva-se 98, while this branch changes no public page. The public transfer sizes also remain at the existing levels; the /news movement is within Lighthouse run variance.

I am leaving that existing frontend-performance work out of this draft so the webhook/security change stays narrowly scoped. No deployment was attempted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant