Webhooks
React to Hub events with signed HTTP callbacks.
Core Concept
Section titled “Core Concept”Webhooks let Hub push events to your system over HTTP as soon as data changes.
Instead of polling the API, you register a URL and Hub sends POST requests to it.
Hub uses the Standard Webhooks signing format, so every delivery includes:
webhook-idwebhook-signaturewebhook-timestamp
and a JSON payload describing the event.
How Webhooks Work in Hub
Section titled “How Webhooks Work in Hub”When a supported event happens, Hub:
- Publishes an internal event (
feedback_record.*orwebhook.*). - Looks up enabled webhooks subscribed to that event type.
- Enqueues one River job per
(event, webhook)pair. - A worker sends a signed
POSTrequest to each webhook URL.
Delivery is asynchronous: the API request that triggered the event does not wait for your webhook endpoint to finish.
Hub also applies a 24-hour uniqueness window per (event_id, webhook_id) at enqueue time.
Supported Event Types
Section titled “Supported Event Types”Hub currently emits these webhook event types:
feedback_record.createdfeedback_record.updatedfeedback_record.deleted(single delete or bulk delete)webhook.createdwebhook.updatedwebhook.deleted
If event_types is omitted, null, or [], the webhook receives all event types.
1. Prepare Hub
Section titled “1. Prepare Hub”Run database migrations and River migrations (required for webhook delivery jobs):
make init-dbmake river-migratemake run2. Create a Receiver Endpoint
Section titled “2. Create a Receiver Endpoint”Create an endpoint in your app (use HTTPS in production) that:
- Accepts
POSTrequests - Reads raw request body bytes
- Verifies Standard Webhooks signature
- Returns a
2xxquickly after accepting work
3. Register a Webhook in Hub
Section titled “3. Register a Webhook in Hub”curl -X POST http://localhost:8080/v1/webhooks \ -H "Authorization: Bearer your-api-key" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hub-webhooks", "event_types": [ "feedback_record.created", "feedback_record.updated" ], "enabled": true }'If you omit signing_key, Hub auto-generates one (whsec_...) and returns it in the response.
4. Verify Signatures in Your Receiver
Section titled “4. Verify Signatures in Your Receiver”Use the same signing key from the webhook record:
package main
import ( "io" "net/http" "os"
standardwebhooks "github.com/standard-webhooks/standard-webhooks/libraries/go")
func handleHubWebhook(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "invalid body", http.StatusBadRequest) return }
wh, err := standardwebhooks.NewWebhook(os.Getenv("HUB_WEBHOOK_SIGNING_KEY")) if err != nil { http.Error(w, "invalid signing key", http.StatusInternalServerError) return }
if err := wh.Verify(body, r.Header); err != nil { http.Error(w, "invalid signature", http.StatusUnauthorized) return }
// Signature is valid. Parse JSON and process the event. w.WriteHeader(http.StatusOK)}5. Process Idempotently
Section titled “5. Process Idempotently”Hub uses one stable event ID for all deliveries of the same event (including retries and different endpoints). Use webhook-id as your idempotency key to safely ignore duplicates.
Delivery Behavior
Section titled “Delivery Behavior”Success Criteria
Section titled “Success Criteria”Any 2xx response is treated as successful delivery.
Retry and Disable Rules
Section titled “Retry and Disable Rules”- Non-2xx responses, network failures, and timeouts are retried by River.
410 Gonedisables the webhook immediately.- If the final retry attempt still fails, Hub disables the webhook and sets:
disabled_reasondisabled_at
- Re-enable with:
curl -X PATCH http://localhost:8080/v1/webhooks/{id} \ -H "Authorization: Bearer your-api-key" \ -H "Content-Type: application/json" \ -d '{"enabled": true}'Re-enabling clears the disabled fields.
Timeouts and Redirects
Section titled “Timeouts and Redirects”- Outbound webhook HTTP timeout: 15 seconds
- Redirects are not followed
Webhook Payload
Section titled “Webhook Payload”Hub sends JSON payloads in this shape:
{ "id": "01952dc6-c331-7a31-97c5-d0f489f9100a", "type": "feedback_record.updated", "timestamp": "2026-02-17T10:12:33Z", "data": { "...": "event-specific payload" }, "changed_fields": ["value_text", "metadata"]}changed_fieldsappears only for update events.- For delete events (
feedback_record.deleted,webhook.deleted),datais an array of deleted UUIDs.
API Endpoints for Managing Webhooks
Section titled “API Endpoints for Managing Webhooks”POST /v1/webhookscreate webhookGET /v1/webhookslist webhooksGET /v1/webhooks/{id}get webhook by IDPATCH /v1/webhooks/{id}update webhookDELETE /v1/webhooks/{id}delete webhook
All management endpoints require Authorization: Bearer <API_KEY>.
Useful Configuration
Section titled “Useful Configuration”# Max concurrent webhook delivery workers (default: 100)WEBHOOK_DELIVERY_MAX_CONCURRENT=100
# Max delivery attempts per webhook job (default: 3)WEBHOOK_DELIVERY_MAX_ATTEMPTS=3
# Insert batch size when fanning out jobs for one event (default: 500)WEBHOOK_MAX_FAN_OUT_PER_EVENT=500
# Max total webhook endpoints allowed (default: 500)WEBHOOK_MAX_COUNT=500
# Event channel buffer size (default: 16384)MESSAGE_PUBLISHER_QUEUE_MAX_SIZE=16384Troubleshooting
Section titled “Troubleshooting”No Webhooks Delivered
Section titled “No Webhooks Delivered”- Confirm River migrations were applied:
make river-migrate - Check webhook is enabled:
GET /v1/webhooks/{id} - Verify
event_typesincludes the emitted event - Ensure your endpoint is reachable from Hub and returns
2xx
Signature Verification Fails
Section titled “Signature Verification Fails”- Confirm you are using that webhook’s
signing_key - Verify against raw body bytes (not transformed JSON)
- Check server clock skew (timestamp tolerance is enforced by Standard Webhooks libraries)
Webhook Became Disabled
Section titled “Webhook Became Disabled”Read disabled_reason and disabled_at from webhook data, fix the endpoint, then re-enable via PATCH with "enabled": true.
Next Steps
Section titled “Next Steps”- Authentication - Secure webhook management endpoints
- Data Model - Understand feedback payload fields
- Environment Variables - Full config reference