Webhooks
Webhooks enable boards to receive events from external systems. Incoming payloads are stored, written to board memory, and agents are notified.
Webhook Workflow
Create webhook
Configure a webhook endpoint for your board:curl -X POST http://localhost:8000/api/v1/boards/<board-id>/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "GitHub push events - create task for each new commit",
"enabled": true,
"agent_id": "<agent-id>"
}'
Get endpoint URL
Response includes the webhook URL:{
"id": "<webhook-id>",
"board_id": "<board-id>",
"agent_id": "<agent-id>",
"description": "GitHub push events",
"enabled": true,
"endpoint_path": "/api/v1/boards/<board-id>/webhooks/<webhook-id>",
"endpoint_url": "http://72.62.201.147:8000/api/v1/boards/<board-id>/webhooks/<webhook-id>"
}
Configure external service
Add the webhook URL to your external service (GitHub, Stripe, etc.):
- GitHub: Repository → Settings → Webhooks → Add webhook
- Stripe: Developers → Webhooks → Add endpoint
- Custom: Send POST requests to the endpoint
Receive events
When an event occurs, Mission Control:
- Validates webhook is enabled
- Captures payload and headers
- Stores in
board_webhook_payloads table
- Writes to board memory with tags
- Notifies target agent (or board lead)
Source: backend/app/api/board_webhooks.py
Create Webhook
curl -X POST http://localhost:8000/api/v1/boards/<board-id>/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "Process new customer signups from Stripe",
"enabled": true,
"agent_id": "<agent-id>"
}'
Fields:
description - Instructions for the agent on how to handle events
enabled - Whether webhook accepts events (default: true)
agent_id - Specific agent to notify (optional, defaults to board lead)
Response:
{
"id": "a7f3c2e1-...",
"board_id": "70a4ea4f-...",
"agent_id": "c91361ef-...",
"description": "Process new customer signups from Stripe",
"enabled": true,
"endpoint_path": "/api/v1/boards/70a4ea4f-.../webhooks/a7f3c2e1-...",
"endpoint_url": "http://72.62.201.147:8000/api/v1/boards/70a4ea4f-.../webhooks/a7f3c2e1-...",
"created_at": "2026-03-05T12:00:00",
"updated_at": "2026-03-05T12:00:00"
}
Source: backend/app/api/board_webhooks.py:289-308
Webhook Endpoint
The webhook endpoint is publicly accessible (no authentication required):
POST /api/v1/boards/<board-id>/webhooks/<webhook-id>
Example Request
GitHub push event:
curl -X POST http://72.62.201.147:8000/api/v1/boards/70a4ea4f-.../webhooks/a7f3c2e1-... \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: push" \
-H "X-GitHub-Delivery: 12345-67890" \
-d '{
"ref": "refs/heads/main",
"commits": [
{
"id": "a3f4b2c",
"message": "Fix memory leak in worker process",
"author": {"name": "Jane Doe", "email": "[email protected]"},
"timestamp": "2026-03-05T12:00:00Z"
}
]
}'
Response:
{
"board_id": "70a4ea4f-...",
"webhook_id": "a7f3c2e1-...",
"payload_id": "b9d2e5f8-..."
}
Status code: 202 Accepted (processing asynchronously)
Source: backend/app/api/board_webhooks.py:425-525
Payload Processing
When a webhook receives a payload:
1. Validation
if not webhook.enabled:
raise HTTPException(
status_code=status.HTTP_410_GONE,
detail="Webhook is disabled.",
)
2. Payload Decoding
Supports JSON and plaintext:
def _decode_payload(
raw_body: bytes,
*,
content_type: str | None,
) -> dict[str, object] | list[object] | str | int | float | bool | None:
# Try JSON parsing if Content-Type suggests JSON or body looks like JSON
# Fall back to plaintext string
Source: backend/app/api/board_webhooks.py:139-160
3. Storage
Stored in board_webhook_payloads:
payload = BoardWebhookPayload(
board_id=board.id,
webhook_id=webhook.id,
payload=payload_value, # JSON or string
headers=captured_headers, # Content-Type, User-Agent, X-*
source_ip=request.client.host,
content_type=content_type,
)
session.add(payload)
4. Board Memory
Creates a memory entry:
memory = BoardMemory(
board_id=board.id,
content=_webhook_memory_content(webhook=webhook, payload=payload),
tags=[
"webhook",
f"webhook:{webhook.id}",
f"payload:{payload.id}",
],
source="webhook",
is_chat=False,
)
Memory content format:
WEBHOOK PAYLOAD RECEIVED
Webhook ID: a7f3c2e1-...
Payload ID: b9d2e5f8-...
Instruction: Process new customer signups from Stripe
Inspect (admin API): /api/v1/boards/.../webhooks/.../payloads/...
Payload preview:
{
"type": "customer.subscription.created",
"data": {...}
}
Source: backend/app/api/board_webhooks.py:185-200
5. Agent Notification
Target agent (or board lead) receives a message:
WEBHOOK EVENT RECEIVED
Board: Customer Success
Webhook ID: a7f3c2e1-...
Payload ID: b9d2e5f8-...
Instruction: Process new customer signups from Stripe
Take action:
1) Triage this payload against the webhook instruction.
2) Create/update tasks as needed.
3) Reference payload ID b9d2e5f8-... in task descriptions.
Payload preview:
{...}
To inspect board memory entries:
GET /api/v1/agent/boards/<board-id>/memory?is_chat=false
Source: backend/app/api/board_webhooks.py:202-251
List Webhooks
GET /api/v1/boards/<board-id>/webhooks
Response:
{
"items": [
{
"id": "a7f3c2e1-...",
"board_id": "70a4ea4f-...",
"agent_id": "c91361ef-...",
"description": "GitHub push events",
"enabled": true,
"endpoint_path": "/api/v1/boards/.../webhooks/...",
"endpoint_url": "http://72.62.201.147:8000/...",
"created_at": "2026-03-05T12:00:00"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
Source: backend/app/api/board_webhooks.py:270-286
Update Webhook
Change description, enabled state, or target agent:
curl -X PATCH http://localhost:8000/api/v1/boards/<board-id>/webhooks/<webhook-id> \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated: Create task for each commit, assign to DevOps",
"enabled": false,
"agent_id": "<different-agent-id>"
}'
Disabling webhooks:
Set enabled: false to stop accepting events. Existing payloads remain accessible.
Source: backend/app/api/board_webhooks.py:326-349
Delete Webhook
Deleting a webhook removes all stored payloads.
curl -X DELETE http://localhost:8000/api/v1/boards/<board-id>/webhooks/<webhook-id> \
-H "Authorization: Bearer $TOKEN"
Cascades to:
- All webhook payloads
- Related board memory entries (if cleanup enabled)
Source: backend/app/api/board_webhooks.py:352-372
View Payloads
List Payloads
GET /api/v1/boards/<board-id>/webhooks/<webhook-id>/payloads
Response:
{
"items": [
{
"id": "b9d2e5f8-...",
"board_id": "70a4ea4f-...",
"webhook_id": "a7f3c2e1-...",
"payload": {
"ref": "refs/heads/main",
"commits": [...]
},
"headers": {
"content-type": "application/json",
"x-github-event": "push",
"user-agent": "GitHub-Hookshot/abc123"
},
"source_ip": "192.30.252.1",
"content_type": "application/json",
"received_at": "2026-03-05T12:00:00"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
Source: backend/app/api/board_webhooks.py:375-400
Get Single Payload
GET /api/v1/boards/<board-id>/webhooks/<webhook-id>/payloads/<payload-id>
Returns full payload details including complete JSON/string body.
Source: backend/app/api/board_webhooks.py:403-422
These headers are automatically captured:
Content-Type
User-Agent
- All headers starting with
X- (e.g., X-GitHub-Event, X-Stripe-Signature)
Source: backend/app/api/board_webhooks.py:163-169
Webhook Security
UUID-Based URLs
Webhook URLs include a UUID that’s hard to guess:
/api/v1/boards/70a4ea4f-5b2d-4f3e-9a1c-8d7e6f4a2b1c/webhooks/a7f3c2e1-4d8f-4a2b-9c5d-3e7f8a1b2c4d
IP Allowlisting
Filter by source IP:
allowed_ips = ["192.30.252.0/24"] # GitHub IPs
if request.client.host not in allowed_ips:
raise HTTPException(status_code=403)
Implementation: Add to webhook model or board configuration.
Signature Verification
For services that send signatures (GitHub, Stripe):
GitHub example:
import hmac
import hashlib
def verify_github_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# In webhook handler:
signature = request.headers.get('X-Hub-Signature-256')
if not verify_github_signature(await request.body(), signature, webhook_secret):
raise HTTPException(status_code=403)
Implementation: Add secret field to webhook model.
Async Processing
Webhook delivery to agents uses Redis Queue (RQ):
enqueued = enqueue_webhook_delivery(
QueuedInboundDelivery(
board_id=board.id,
webhook_id=webhook.id,
payload_id=payload.id,
received_at=payload.received_at,
),
)
Benefits:
- Immediate 202 response to external service
- Retry logic for failed notifications
- Doesn’t block webhook ingestion
Source: backend/app/api/board_webhooks.py:495-519, backend/app/services/webhooks/queue.py
Common Integration Examples
GitHub Push Events
Webhook description:
Create a task for each commit pushed to main branch.
Task title: "Review commit: <commit_message>"
Assign to code review agent.
Agent action:
- Parse
commits array from payload
- For each commit:
- Create task with commit message
- Add commit SHA and author to task description
- Link to GitHub commit URL
Stripe Payment Events
Webhook description:
When customer.subscription.created:
- Create onboarding task for new customer
- Notify sales agent
- Update CRM
Agent action:
- Check
type field in payload
- Extract customer email and plan details
- Create task: “Onboard new customer: ”
- Post to board memory with customer context
Custom API Webhooks
Webhook description:
Monitoring alert received.
If severity=critical:
- Create urgent task
- Notify on-call agent immediately
Else:
- Log to board memory for review
Agent action:
- Parse
severity from payload
- If critical, create high-priority task
- Otherwise, just acknowledge in memory
Database Schema
CREATE TABLE board_webhooks (
id UUID PRIMARY KEY,
board_id UUID REFERENCES boards(id),
agent_id UUID REFERENCES agents(id) NULL,
description TEXT NOT NULL,
enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
CREATE TABLE board_webhook_payloads (
id UUID PRIMARY KEY,
board_id UUID REFERENCES boards(id),
webhook_id UUID REFERENCES board_webhooks(id),
payload JSONB,
headers JSONB,
source_ip TEXT,
content_type TEXT,
received_at TIMESTAMP
);
Source: backend/app/models/board_webhooks.py, backend/app/models/board_webhook_payloads.py
Best Practices
For Webhook Configuration
- Clear descriptions: Write detailed instructions for agents
- Target specific agents: Use
agent_id for specialized handlers
- Disable when not needed: Set
enabled: false to pause
- Monitor payload history: Review stored payloads regularly
For Agent Handlers
- Validate payload structure: Check for expected fields
- Handle errors gracefully: Don’t crash on unexpected data
- Reference payload IDs: Include in task descriptions for traceability
- Query board memory: Check for previous related events
For External Services
- Test with sample payloads: Send test events first
- Configure retry logic: Most services retry failed webhooks
- Monitor delivery: Check service’s webhook logs for failures
- Verify signatures: Implement signature verification for security
See Also