Webhook System
Status: ✅ Implemented — CRUD, HMAC delivery, retries, Settings UI, and event triggers including Brain form submissions
Last Updated: 2026-08-18
Subscription Required: Yes (subscribed tier feature)
Overview
The Webhook System allows subscribed users to receive real-time notifications when events occur in ARC // OS. Users can configure webhook endpoints that receive HTTP POST requests when specific events happen (e.g., task completed, training session logged, checklist item checked).
Key Features:
- Event-driven - Real-time notifications for user actions
- Secure - Webhook secret verification for authenticity
- Reliable - Retry logic with exponential backoff for failed deliveries
- Trackable - Delivery history for debugging and monitoring
- User-controlled - Users create and manage their own webhooks
Architecture
Database Schema
model Webhook {
id String @id @default(uuid())
userId String
url String // Webhook endpoint URL
events String[] // Array of event types to subscribe to
secret String // Secret for webhook signature verification
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
deliveries WebhookDelivery[] // Delivery history
@@index([userId])
@@index([active])
@@map("webhooks")
}
model WebhookDelivery {
id String @id @default(uuid())
webhookId String
event String // Event type that triggered this delivery
payload Json // Event payload (sanitized)
status String // 'pending', 'success', 'failed'
statusCode Int? // HTTP status code from webhook endpoint
responseBody String? // Response body from webhook endpoint
attempts Int @default(0) // Number of delivery attempts
lastAttemptAt DateTime?
nextRetryAt DateTime? // When to retry (if failed)
deliveredAt DateTime? // When successfully delivered
createdAt DateTime @default(now())
webhook Webhook @relation(fields: [webhookId], references: [id], onDelete: Cascade)
@@index([webhookId])
@@index([status])
@@index([nextRetryAt]) // For retry scheduling
@@map("webhook_deliveries")
}
Service Layer
WebhookService (server/src/services/webhook.service.ts)
Methods:
createWebhook(userId, url, events)- Create new webhook endpointupdateWebhook(userId, webhookId, updates)- Update webhook configurationdeleteWebhook(userId, webhookId)- Delete webhooklistWebhooks(userId)- Get user's webhookstriggerWebhook(webhookId, event, data)- Send webhook event (internal)verifyWebhookSecret(webhookId, signature, payload)- Verify webhook signaturegetDeliveryHistory(userId, webhookId?)- Get delivery historyretryFailedDeliveries()- Retry failed webhook deliveries (background job)
WebhookEventService (orchestrator)
Purpose: Triggers webhooks when events occur throughout the system.
Integration Points:
- Task completion → trigger
task_completedwebhook - Training session logged → trigger
training_loggedwebhook - Checklist item checked → trigger
checklist_completedwebhook - Cardio session logged → trigger
cardio_loggedwebhook - Water intake logged → trigger
water_loggedwebhook - Meditation session logged → trigger
meditation_loggedwebhook - Day completed → trigger
day_completedwebhook - Achievement unlocked → trigger
achievement_unlockedwebhook - Brain form submitted → trigger
brain_form_submittedwebhook (form owner)
Webhook Events
Event Types
Task Events:
task_completed- Task marked as donetask_deferred- Task deferred with next steptask_created- New task created
Training Events:
training_logged- Training session loggedtraining_updated- Training session updated
Activity Events:
checklist_completed- Checklist item checkedcardio_logged- Cardio session loggedwater_logged- Water intake loggedmeditation_logged- Meditation session logged
Day Events:
day_completed- Day marked as completeminimum_mode_toggled- Minimum mode enabled/disabled
Gamification Events:
achievement_unlocked- Achievement unlockedlevel_up- User leveled upstreak_updated- Streak updated
Social Events:
friend_added- Friend connection establishedmessage_received- New message received
Brain Events:
brain_form_submitted- A Brain form created a database record (public link, in-app, or MCP). Payload includes answers. Fired for the form owner, not the respondent.
Event Payload Format
All webhook events follow a consistent payload structure:
{
"event": "task_completed",
"timestamp": "2026-01-23T10:30:00.000Z",
"data": {
// Event-specific data (sanitized, no sensitive information)
"taskId": "uuid",
"title": "Task title",
"completedAt": "2026-01-23T10:30:00.000Z"
}
}
Sanitization Rules:
- No user email addresses
- No sensitive personal information
- Most events: IDs and non-sensitive metadata only
brain_form_submitted: includes form answers (needed to fan out to chat/CRM). Respondent fingerprint fields (__respondentKey, IP hash, signal arrays) are stripped; display name and quiz scores are kept- Consistent envelope (
event,timestamp,data) across all events
API Endpoints
Webhook Management
GET /api/v1/webhooks
- List user's webhooks
- Returns: Array of webhook objects with delivery stats
POST /api/v1/webhooks
- Create new webhook
- Body:
{ url: string, events: string[] } - Returns: Webhook object with secret (only shown once)
PUT /api/v1/webhooks/:webhookId
- Update webhook configuration
- Body:
{ url?: string, events?: string[], active?: boolean } - Returns: Updated webhook object
DELETE /api/v1/webhooks/:webhookId
- Delete webhook
- Returns: Success message
GET /api/v1/webhooks/:webhookId/deliveries
- Get delivery history for webhook
- Query params:
status,event,limit,page - Returns: Paginated delivery history
POST /api/v1/webhooks/:webhookId/test
- Send test webhook event
- Body:
{ event: string }(optional, defaults to test event) - Returns: Delivery result
Webhook Delivery
Webhook Delivery Process:
- Event occurs in ARC // OS
WebhookEventServiceidentifies active webhooks subscribed to event- For each webhook:
- Create
WebhookDeliveryrecord (status: 'pending') - Generate webhook signature
- Send HTTP POST to webhook URL
- Update delivery record with response
- Create
- If delivery fails:
- Schedule retry with exponential backoff
- Update
nextRetryAtfield - Background job retries failed deliveries
Security
Webhook Secret Verification
Signature Generation:
- Use HMAC-SHA256 with webhook secret
- Signature:
HMAC-SHA256(payload, secret) - Include signature in
X-ARCOS-Signatureheader
Verification:
- User's webhook endpoint should verify signature
- Compare received signature with computed signature
- Use constant-time comparison to prevent timing attacks
Example Verification (Node.js):
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(computed)
);
}
Best Practices
- Use HTTPS - Webhook URLs must use HTTPS (enforced)
- Verify Signatures - Always verify webhook signatures
- Idempotency - Handle duplicate webhook deliveries gracefully
- Timeouts - Respond quickly (within 5 seconds recommended)
- Error Handling - Return appropriate HTTP status codes
Retry Logic
Exponential Backoff
Retry Schedule:
- Initial retry: 1 minute after failure
- 2nd retry: 2 minutes after first retry
- 3rd retry: 4 minutes after second retry
- 4th retry: 8 minutes after third retry
- 5th retry: 16 minutes after fourth retry
- Maximum: 5 retry attempts
Retry Conditions:
- HTTP 5xx errors (server errors)
- Network timeouts
- Connection failures
No Retry:
- HTTP 2xx (success)
- HTTP 4xx (client errors - don't retry)
- HTTP 401/403 (authentication/authorization - don't retry)
Background Job:
- Runs every minute
- Finds deliveries with
status: 'failed'andnextRetryAt <= now() - Retries up to maximum attempts
- After max attempts, marks as permanently failed
Delivery History
Tracking
Delivery Record Fields:
status- 'pending', 'success', 'failed'statusCode- HTTP status code from endpointresponseBody- Response body (truncated to 1000 chars)attempts- Number of delivery attemptslastAttemptAt- Timestamp of last attemptdeliveredAt- Timestamp when successfully delivered
Viewing History:
- Users can view delivery history for each webhook
- Filter by status, event type, date range
- See response codes and error messages
- Identify patterns in failures
Frontend UI
Webhook Management Page
Location: Settings → Developer (subscribed users only)
Features:
- List all webhooks with status indicators
- Create new webhook form:
- URL input (must be HTTPS)
- Event selection (multi-select)
- Active toggle
- Edit webhook configuration
- Delete webhook (with confirmation)
- View delivery history
- Test webhook button
Delivery History View:
- Table of delivery attempts
- Status indicators (success/failed/pending)
- Response codes
- Timestamps
- Filter by status, event, date range
Webhook Status Indicators:
- 🟢 Active - Webhook is active and receiving events
- 🔴 Inactive - Webhook is disabled
- ⚠️ Failed - Recent delivery failures (last 5 attempts failed)
- ✅ Healthy - All recent deliveries successful
Implementation status
All phases below shipped. How to receive and use deliveries: Outgoing webhooks.
- Database
Webhook/WebhookDeliverymodels WebhookServiceCRUD, HMAC-SHA256 delivery, retriesWebhookEventServicetriggers from domain services- Routes under
/api/v1/webhooks(subscription-gated) - Settings → Developer UI, including test delivery
brain_form_submittedfromFormService.processFormSubmission
Usage Examples
Creating a Webhook
Request:
POST /api/v1/webhooks
Content-Type: application/json
Authorization: Bearer <jwt-token>
{
"url": "https://myapp.com/webhooks/arcos",
"events": ["task_completed", "training_logged"]
}
Response:
{
"ok": true,
"data": {
"id": "webhook-uuid",
"url": "https://myapp.com/webhooks/arcos",
"events": ["task_completed", "training_logged"],
"secret": "whsec_AbCdEf123456...",
"active": true,
"createdAt": "2026-01-23T10:00:00.000Z"
}
}
⚠️ Important: The secret is only returned once on creation. Save it immediately.
Receiving a Webhook
HTTP Request:
POST https://myapp.com/webhooks/arcos
Content-Type: application/json
X-ARCOS-Signature: sha256=AbCdEf123456...
X-ARCOS-Event: task_completed
X-ARCOS-Delivery-Id: delivery-uuid
{
"event": "task_completed",
"timestamp": "2026-01-23T10:30:00.000Z",
"data": {
"taskId": "task-uuid",
"title": "Finish project proposal",
"completedAt": "2026-01-23T10:30:00.000Z"
}
}
Brain form submissions use the same envelope. X-ARCOS-Event is brain_form_submitted; data.values holds answers (fingerprint fields stripped). Full recipe: Outgoing webhooks.
Verification:
const signature = request.headers['x-arcos-signature'];
const payload = request.body;
const secret = 'whsec_AbCdEf123456...'; // Your webhook secret
if (!verifyWebhookSignature(payload, signature, secret)) {
return response.status(401).send('Invalid signature');
}
// Process webhook event
console.log('Event:', payload.event);
console.log('Data:', payload.data);
Response:
- Return
200 OKto acknowledge receipt - Return
4xxfor client errors (won't retry) - Return
5xxfor server errors (will retry)
Design Decisions
Why Webhooks?
- Real-time - Immediate notifications when events occur
- Decoupled - User's systems don't need to poll
- Efficient - Only send data when events happen
- Flexible - Users can integrate with any system
Why Subscription-Only?
- Resource intensive - Webhook delivery requires infrastructure
- Advanced feature - Primarily for power users and integrations
- Value-add - Provides clear value for subscription tier
Why Sanitized Payloads?
- Privacy - Never expose sensitive user data
- Consistency - Same structure across all events
- Security - Minimize data exposure
- Compliance - Reduce privacy risks
Why Retry Logic?
- Reliability - Network issues are temporary
- User experience - Ensure webhooks are delivered
- Transparency - Users can see delivery status
Why Delivery History?
- Debugging - Users can troubleshoot webhook issues
- Transparency - See what was sent and when
- Monitoring - Track webhook health
- Accountability - Audit trail of deliveries
Future Enhancements
- Webhook Templates - Pre-configured webhook setups for common integrations
- Webhook Transformations - Custom payload transformations
- Webhook Filtering - Filter events by conditions (e.g., only high-priority tasks)
- Webhook Queuing - Queue webhooks for batch processing
- Webhook Analytics - Statistics on webhook usage and delivery rates
- Direct destinations - Native third-party connectors (today: your server is the bridge)
- Webhook Logs - Detailed logs for debugging
Security Considerations
HTTPS Requirement
- All webhook URLs must use HTTPS
- Prevents man-in-the-middle attacks
- Ensures payload encryption in transit
Secret Management
- Secrets are generated using cryptographically secure random
- Secrets are hashed before storage (similar to API keys)
- Secrets are only shown once on creation
- Secrets can be rotated (regenerate secret)
Rate Limiting
- Webhook delivery should respect rate limits
- Prevent abuse of webhook system
- Configurable limits per user
Payload Sanitization
- Never include sensitive data
- Only include IDs and non-sensitive metadata
- Consistent sanitization across all events
- Review payloads before adding new events
Integration Points
Where Webhooks Are Triggered
TaskService:
updateTask()- Triggertask_completedortask_deferredwhen status changes
TrainingService:
createSession()- Triggertraining_loggedwhen session createdupdateSession()- Triggertraining_updatedwhen session updated
ChecklistService:
toggleItem()- Triggerchecklist_completedwhen item checked
CardioService:
createSession()- Triggercardio_loggedwhen session created
WaterIntakeService:
create()- Triggerwater_loggedwhen intake logged
DayService:
updateDay()- Triggerday_completedorminimum_mode_toggledwhen relevant
GamificationService:
unlockAchievement()- Triggerachievement_unlockedlevelUp()- Triggerlevel_upupdateStreak()- Triggerstreak_updated
FormService:
processFormSubmission()- Triggerbrain_form_submittedafter a record is created (public slug, authenticated, or MCP)
Testing Strategy
Unit Tests
- WebhookService methods
- Signature generation/verification
- Retry logic calculations
- Payload sanitization
Integration Tests
- Webhook creation/update/delete
- Webhook triggering
- Delivery history
- Retry mechanism
E2E Tests
- Complete webhook flow (create → receive → verify)
- Delivery history viewing
- Webhook management UI
Configure endpoints in Settings → Developer. Recipe for pushing Brain form answers into an external chat API: Outgoing webhooks.