webhooks scoped to conversations, not projects
When we added webhooks to ECHO, first question was scope. Do webhooks fire at the project level (any conversation triggers it) or conversation level (specific conversation triggers it)?
Went with conversation-level.
ECHO runs town halls and community consultations. A single project might have 200 conversations across different locations, topics, or time slots. A project-level webhook firing on every conversation event would be unusable. Hundreds of notifications with no way to filter by the conversation that matters.
Conversation-level webhooks let integrators subscribe to specific conversations. A municipality running a town hall in three districts can set up different webhooks for each district’s conversation stream.
Implementation:
Backend: webhook.py (399 lines) handles delivery with standard retry/backoff. project_webhook.py (442 lines) manages CRUD scoped to project_id + conversation_id.
Frontend: WebhookSettingsCard.tsx (879 lines). Yeah, the settings UI is bigger than the backend. Per-conversation toggle states, URL validation, event type selection, test delivery functionality. UI complexity always exceeds backend complexity for config screens.
Gated by ENABLE_WEBHOOKS feature flag. Shipped it dark, enabled for specific partners, then rolled out broadly once delivery reliability was proven.
Analytics: every webhook action fires an analytics event. Creation, deletion, test sends, delivery failures. Overkill for launch but invaluable for debugging “my webhook stopped working” tickets. Can trace exactly when a webhook was modified and whether deliveries are succeeding.
Decoupled from transcription. The webhook fires when a conversation event completes, not during transcription. The transcription pipeline is already complex (chunking, processing, PII redaction). Adding webhook delivery as a pipeline step would mean webhook failures could block transcription. Instead, webhooks subscribe to the completed event and fire independently.
Small delay between transcription completing and webhook firing. In practice it’s milliseconds, but the decoupling matters. Transcription pipeline stays pure, webhook delivery can be retried without affecting core data flow.
Across all 6 supported languages (en-US, de-DE, es-ES, fr-FR, it-IT, nl-NL), the webhook payload includes original language and any translations. Integrators get localized data without calling back into our API.