Webhook Sensor
What it ingests
Arbitrary events from your own integrations — incidents, notes, deploys, or anything else you can POST as JSON. The source is webhook-driven: your producer POSTs to Corveil, so there's no outbound polling and firewalled systems work the same way Jenkins does.
This is one sensor. Zoom transcripts, Gong, Greenhouse, Salesforce, and the local file watcher remain separate catalog entries and are not bundled here.
Credential
None to create. The webhook token is generated server-side at create time and stored as a hash. The plaintext token is returned once in the create response so the UI can show you the full webhook URL — copy it then; it isn't shown again.
HMAC signatures are out of scope for v1. The URL token is the auth.
Config fields
The only config field is optional.
| Key | Notes |
|---|---|
source_label | Human-readable name shown in the UI, e.g. pager. Display-only; not used for auth. |
The Add-sensor form also offers a default entity type picker. Leave it blank to land each POST as an Event. Set it to retarget the primary entity (Meeting, CreativeWork, Issue, …) without changing the producer payload.
Wire-up
POST /api/sensors
{
"sensor_type": "webhook",
"name": "Pager",
"config": {
"source_label": "pager"
}
}The response includes the generated webhook URL + token. Save it. If you lose it, Edit sensor → Get webhook URL (or POST /api/sensors/{id}/rotate-webhook) issues a new URL and immediately invalidates the previous one.
Payload
POST /webhooks/webhook/{token} with Content-Type: application/json. Maximum body size is 256 KiB.
{
"id": "unique-in-this-sensor",
"title": "required",
"summary": "",
"content": "",
"url": "",
"actor_email": "",
"actor_name": "",
"occurred_at": "2026-09-05T12:00:00Z",
"event_type": "event",
"category": "document"
}| Field | Required | Notes |
|---|---|---|
id | yes | Unique per sensor. Aliases: external_id. Stored as webhook:{id} (no double-prefix if you already send that). Replaying the same id is a 200, not a new row. |
title | yes | Display name of the resulting entity (truncated at 500 characters). |
summary | no | Short description; falls back to title. |
content / body | no | Longer body text. |
url | no | Link back to the source system. |
actor_email / email | no | Resolved to a Person when present. |
actor_name / name | no | Display name; falls back to the email local-part. |
occurred_at | no | RFC3339 or unix seconds (milliseconds if > 1e12). Omitted → ingest time. |
event_type / type | no | Lowercase slug ^[a-z][a-z0-9_]{0,63}$. Default event. |
category | no | document (default), code, conversation, task, meeting, or file. Unknown values become document. |
Extra JSON fields are ignored.
Provider-side setup
Point your producer at the webhook URL from the create response. A typical curl:
curl -sS -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{"id":"inc-42","title":"Pager fired","actor_email":"alice@acme.com"}'A 200 means the event was accepted (including an idempotent replay of the same id). 401 is an unknown or rotated token. 400 is a malformed or oversized body. 429 is rate limiting (120 requests/minute per IP before lookup; 600 per sensor after).
Verify
POST a payload, then check Sensors for a recent "received" time and the ontology for an Event (or whatever default entity type you picked) named after title. If nothing arrives, rotate the URL and update the producer — the previous token stops working immediately.