Merged
Conversation
- Add Event and MessagePublisher interface with MessagePublisherManager - Add event type enum (internal/datatypes/event_type.go) for feedback_record and future webhook event types - Add placeholder EmailDeliveryService implementing MessagePublisher - Wire FeedbackRecordsService to publish events on create/update/delete - Wire message manager in main and integration tests (no webhook provider yet) Co-authored-by: Cursor <cursoragent@cursor.com>
- Use goose for app schema (migrations/001, 002_webhooks) - Single 002_webhooks.sql migration with full webhooks table and indexes - Keep river-migrate target and add to CI after init-db - AGENTS.md: document river-migrate for webhook delivery Co-authored-by: Cursor <cursoragent@cursor.com>
BhagyaAmarasinghe
requested changes
Feb 9, 2026
Contributor
BhagyaAmarasinghe
left a comment
There was a problem hiding this comment.
Thanks for the PR :)
I have added several comments that needs to be looked.
BhagyaAmarasinghe
approved these changes
Feb 10, 2026
Contributor
BhagyaAmarasinghe
left a comment
There was a problem hiding this comment.
Thank you, LGTM 🚀
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Standard Webhooks support for Hub events, based on River as a queue.
Event / Webhooks pipeline (architecture)
POST /v1/feedback-records,DELETE /v1/webhooks/{id}). The handler calls the corresponding service.PublishEvent(FeedbackRecordCreated, record)). This is fire-and-forget into an in-memory channel; the API does not wait for webhook delivery.MESSAGE_PUBLISHER_PER_EVENT_TIMEOUT_SECONDS(default 10s) so one provider doesn’t block others. If the channel is full, the event is dropped and a warning is logged. Channel buffer size is configurable viaMESSAGE_PUBLISHER_BUFFER_SIZE(default 1024).WEBHOOK_MAX_FAN_OUT_PER_EVENT, default 500), and calls River’s InsertMany once. No HTTP here; only list + enqueue. Jobs are unique by (event id, webhook id) for 24 hours.webhook-id,webhook-signature,webhook-timestamp), POSTs to the webhook URL. 2xx → job complete. 410 Gone → webhook disabled in DB, no retry. Other failures → retry with backoff until max attempts; after last failure the webhook can be disabled.In short: API → Service → MessagePublisherManager (channel) → WebhookProvider (DB + River InsertMany) → River queue → WebhookDispatchWorker → WebhookSender → HTTP POST. The slow/unreliable part (HTTP to third-party URLs) stays in the worker and sender; the API stays non-blocking.
What’s in this branch
Webhooks
POST/GET/PATCH/DELETE /v1/webhooks, with list/filter support.feedback_record.created,feedback_record.updated,feedback_record.deleted(and webhook lifecycle events).WEBHOOK_MAX_FAN_OUT_PER_EVENT, default 500) to avoid blocking the publisher when an event has many webhooks.002_webhooks.sql,003_webhook_disabled_state.sql(and dependency on goose migrations).openapi.yaml.Infrastructure & config
WebhookDispatchWorkerincmd/api/main.go; webhook provider enqueues jobs via InsertMany (one batch per event, capped byWEBHOOK_MAX_FAN_OUT_PER_EVENT).riveruiservice for the River UI (port 8081), with basic auth viaRIVER_BASIC_AUTH_USER/RIVER_BASIC_AUTH_PASS(no defaults in compose).WEBHOOK_DELIVERY_MAX_CONCURRENT,WEBHOOK_DELIVERY_MAX_ATTEMPTS,WEBHOOK_MAX_FAN_OUT_PER_EVENT;MESSAGE_PUBLISHER_BUFFER_SIZE,MESSAGE_PUBLISHER_PER_EVENT_TIMEOUT_SECONDS,SHUTDOWN_TIMEOUT_SECONDS;.env.exampleupdated with River UI auth and the above vars.Other
tests/integration_test.go.How to try it
make init-dbandmake river-migrate.docker compose up -dfor Postgres + River UI (set River UI auth in.env).feedback_record.created) and create a feedback record; confirm delivery in River UI and at the webhook URL.