Skip to content

Twitch: Webhook Ingestion Endpoint (data lake) #268

@danielhe4rt

Description

@danielhe4rt

Parent

#266 — Twitch EventSub: ingestão de eventos via webhook (data lake)

What to build

A public HTTPS endpoint that receives Twitch EventSub webhook notifications, verifies their HMAC-SHA256 signature, and stores the raw payload in a twitch_event_logs table. Pure data lake — no processing on write.

End-to-end behavior

After this slice, Twitch can POST EventSub notifications to /api/webhooks/twitch/eventsub. The endpoint handles three message types:

  • Verification challenge: returns the challenge string (200, text/plain) to activate new subscriptions
  • Notification: stores the full payload in twitch_event_logs and returns 204
  • Revocation: stores the revocation payload and returns 204

Invalid signatures, missing headers, expired timestamps (>10 min), and duplicate messages are all rejected or handled gracefully.

What changes

New — Migration:

  • twitch_event_logs table: id (bigint PK), event_type (string, indexed), broadcaster_user_id (string, nullable, indexed), user_id (string, nullable), twitch_message_id (string, nullable, unique), payload (jsonb), timestamps

New — Model:

  • TwitchEventLog in Models/ — simple model with payload cast to array, mirrors DiscordEventLog

New — Middleware:

  • VerifyTwitchSignature in Http/Middleware/ — verifies Twitch-Eventsub-Message-Signature header using HMAC-SHA256 (hash_hmac + hash_equals), rejects missing headers (403), rejects timestamps older than 10 minutes (403)

New — Controller:

  • TwitchWebhookController in Http/Controllers/ — single __invoke method handling all three message types. Extracts event_type, broadcaster_user_id, user_id from the payload envelope. Deduplication via twitch_message_id unique constraint (catches unique violation, returns 204).

New — Route:

  • routes/twitch-webhook-routes.phpPOST /api/webhooks/twitch/eventsub with VerifyTwitchSignature middleware. Auto-discovered by internachi/modular.

Acceptance criteria

  • twitch_event_logs table created with correct schema (event_type indexed, broadcaster_user_id indexed, twitch_message_id unique, payload jsonb)
  • TwitchEventLog model persists and casts payload to array
  • VerifyTwitchSignature middleware rejects: missing headers (403), invalid signature (403), expired timestamp >10 min (403)
  • VerifyTwitchSignature passes valid HMAC-SHA256 signed requests
  • Controller returns challenge string as text/plain 200 for webhook_callback_verification
  • Controller creates TwitchEventLog and returns 204 for notification type
  • Controller creates TwitchEventLog and returns 204 for revocation type
  • Controller extracts event_type, broadcaster_user_id, user_id from payload into dedicated columns
  • Duplicate twitch_message_id returns 204 without error (idempotent)
  • Route accessible at POST /api/webhooks/twitch/eventsub
  • Tests: full webhook security suite (valid sig, invalid sig, missing headers, expired timestamp, replay)
  • Tests: ingestion suite (notification stored, revocation stored, challenge returned, dedup works)
  • vendor/bin/pint --dirty --format agent passes
  • php artisan test --compact --filter=Twitch passes

Blocked by

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions