Skip to main content

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 endpoint
  • updateWebhook(userId, webhookId, updates) - Update webhook configuration
  • deleteWebhook(userId, webhookId) - Delete webhook
  • listWebhooks(userId) - Get user's webhooks
  • triggerWebhook(webhookId, event, data) - Send webhook event (internal)
  • verifyWebhookSecret(webhookId, signature, payload) - Verify webhook signature
  • getDeliveryHistory(userId, webhookId?) - Get delivery history
  • retryFailedDeliveries() - Retry failed webhook deliveries (background job)

WebhookEventService (orchestrator)

Purpose: Triggers webhooks when events occur throughout the system.

Integration Points:

  • Task completion → trigger task_completed webhook
  • Training session logged → trigger training_logged webhook
  • Checklist item checked → trigger checklist_completed webhook
  • Cardio session logged → trigger cardio_logged webhook
  • Water intake logged → trigger water_logged webhook
  • Meditation session logged → trigger meditation_logged webhook
  • Day completed → trigger day_completed webhook
  • Achievement unlocked → trigger achievement_unlocked webhook
  • Brain form submitted → trigger brain_form_submitted webhook (form owner)

Webhook Events​

Event Types​

Task Events:

  • task_completed - Task marked as done
  • task_deferred - Task deferred with next step
  • task_created - New task created

Training Events:

  • training_logged - Training session logged
  • training_updated - Training session updated

Activity Events:

  • checklist_completed - Checklist item checked
  • cardio_logged - Cardio session logged
  • water_logged - Water intake logged
  • meditation_logged - Meditation session logged

Day Events:

  • day_completed - Day marked as complete
  • minimum_mode_toggled - Minimum mode enabled/disabled

Gamification Events:

  • achievement_unlocked - Achievement unlocked
  • level_up - User leveled up
  • streak_updated - Streak updated

Social Events:

  • friend_added - Friend connection established
  • message_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:

  1. Event occurs in ARC // OS
  2. WebhookEventService identifies active webhooks subscribed to event
  3. For each webhook:
    • Create WebhookDelivery record (status: 'pending')
    • Generate webhook signature
    • Send HTTP POST to webhook URL
    • Update delivery record with response
  4. If delivery fails:
    • Schedule retry with exponential backoff
    • Update nextRetryAt field
    • 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-Signature header

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​

  1. Use HTTPS - Webhook URLs must use HTTPS (enforced)
  2. Verify Signatures - Always verify webhook signatures
  3. Idempotency - Handle duplicate webhook deliveries gracefully
  4. Timeouts - Respond quickly (within 5 seconds recommended)
  5. 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' and nextRetryAt <= 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 endpoint
  • responseBody - Response body (truncated to 1000 chars)
  • attempts - Number of delivery attempts
  • lastAttemptAt - Timestamp of last attempt
  • deliveredAt - 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 / WebhookDelivery models
  • WebhookService CRUD, HMAC-SHA256 delivery, retries
  • WebhookEventService triggers from domain services
  • Routes under /api/v1/webhooks (subscription-gated)
  • Settings → Developer UI, including test delivery
  • brain_form_submitted from FormService.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 OK to acknowledge receipt
  • Return 4xx for client errors (won't retry)
  • Return 5xx for 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() - Trigger task_completed or task_deferred when status changes

TrainingService:

  • createSession() - Trigger training_logged when session created
  • updateSession() - Trigger training_updated when session updated

ChecklistService:

  • toggleItem() - Trigger checklist_completed when item checked

CardioService:

  • createSession() - Trigger cardio_logged when session created

WaterIntakeService:

  • create() - Trigger water_logged when intake logged

DayService:

  • updateDay() - Trigger day_completed or minimum_mode_toggled when relevant

GamificationService:

  • unlockAchievement() - Trigger achievement_unlocked
  • levelUp() - Trigger level_up
  • updateStreak() - Trigger streak_updated

FormService:

  • processFormSubmission() - Trigger brain_form_submitted after 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.