Docs · Protocols
Routine webhooks
A routine is an automation that belongs to your account: it runs an instruction, with or without a model, and delivers the result to one or more channels. A routine can start on a schedule, on an incoming e-mail, or on an HTTP call from your system. This page covers the last one, and the routines API you need to set it up.
Create a routine that a webhook starts#
POST/api/v1/routines#
Authenticated with your API key. Send "trigger": "webhook":
curl -s "$SHVIA_URL/api/v1/routines" \
-H "Authorization: Bearer $SHVIA_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "Deploy notes",
"prompt": "Summarize this deployment event for the team in 2 sentences.",
"trigger": "webhook",
"delivery_targets": ["inapp"]
}'| Field | Notes |
|---|---|
name | required, up to 120 characters |
prompt | required, up to 8000 characters. The body of each event is added to it as data |
trigger | schedule (the default), webhook or email |
delivery_targets | required, 1 to 5 entries: inapp, email:<address>, webhook:https://…, and others that need a channel you have linked, such as telegram and whatsapp:<number> |
uses_ai | whether a model processes the event. Defaults to true. With false no model is called: your prompt is delivered as it is on each event, and the body of the event is ignored |
is_enabled | defaults to true. A routine that fails 3 times in a row is switched off |
webhook_secret | your own signing secret, up to 128 characters. See below |
You can have 10 routines by default. The size limit of 65536 bytes, the 10 minutes of the duplicate window and the 30 events per minute are defaults too, and the operator can change them.
The reply is 201:
{
"routine": { "id": 1, "name": "Deploy notes", "trigger": "webhook", "is_enabled": true, "webhook_token": "<48 hex characters>" },
"webhook_url": "https://ai.shvia.org/api/v1/hooks/<48 hex characters>",
"webhook_secret": "<64 hex characters>",
"webhook_assinado": true,
"webhook_aviso": null
}The routine object is shortened here. webhook_assinado says whether the webhook checks a signature, and webhook_aviso carries a warning when it does not. Three things matter:
webhook_urlcarries a secret token in its path, which is the credential for sending events. Treat the whole URL as a secret. Its host is the installation's own address.- The same token is also in
routine.webhook_token, in this response and in every response toPUT /api/v1/routines/{id}. Do not log those responses whole. webhook_secretis shown once. Without awebhook_secretin your request, the server makes one and returns it. Send"webhook_secret": nullto create an unsigned webhook on purpose, which then answers to anyone who knows the URL. A secret you choose yourself is not returned.
Send an event#
POST/api/v1/hooks/{token}#
No other authentication. The token in the path identifies the routine.
- The body can be anything, up to 65536 bytes: JSON is passed to the routine pretty-printed, other content as text, and an empty body counts as a ping.
- The limit is 120 requests per minute per address, and each routine accepts 30 events per minute by default. Past the limit per address the answer is the plain
{"message": "Too Many Attempts."}.
Sign the body#
When the routine has a secret, which is the default, each event must carry an HMAC-SHA256 of the exact bytes of the body, as lowercase hex, in X-Shvia-Signature. The header X-Hub-Signature-256 is accepted as well, so GitHub webhooks work as they are. The sha256= prefix is optional.
BODY='{"event":"deploy","id":"evt-1"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | sed 's/^.* //')
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-H "X-Shvia-Signature: sha256=$SIG" \
-d "$BODY"{ "ok": true, "queued": true, "event_id": "evt-1" }Sign the body you actually send. Re-serializing the JSON changes the bytes and the signature no longer matches.
Duplicates#
Each event has an id, taken from the first of these that is present: the headers X-Event-Id, X-Request-Id, X-Delivery-Id or X-GitHub-Delivery, then a id, event_id or uuid in a JSON body, and failing that a hash of the body. The same id is ignored for 10 minutes by default, so a retry from your sender is safe. The same rule means that two different events with exactly the same body, and no id of their own, count as duplicates within that time, and so do two empty pings.
Responses#
The checks run in this order:
| Status | Body | When |
|---|---|---|
| 200 | {"ok": true, "ignored": "disabled"} | webhooks are switched off on this installation. This answer does not look at the token |
| 404 | {"error": "not_found", …} | no routine has this token. A token that is not 32 to 64 lowercase hex characters gets the framework's plain 404 instead |
| 200 | {"ok": true, "ignored": "disabled_routine"} | the routine is paused |
| 413 | {"error": "payload_too_large", …} | the body is over 65536 bytes |
| 401 | {"error": "unauthorized", …} | the signature is wrong or missing |
| 200 | {"ok": true, "ignored": "duplicate", "event_id": "…"} | an event with this id was seen in the last 10 minutes |
| 200 | {"ok": true, "ignored": "rate_limited"} | the routine received more than 30 events in a minute |
| 200 | {"ok": true, "queued": true, "event_id": "…"} | accepted |
The event reaches the routine as data, not as instructions: the body of an event is wrapped as untrusted content, and your prompt stays the instruction.
Manage routines#
All of these take your API key and only reach your own routines. Another person's routine is a 404.
| Request | What it does |
|---|---|
GET /api/v1/routines | lists your routines. It never shows a secret or a token |
PUT /api/v1/routines/{id} | changes a routine. {"webhook_secret": null} removes the signature |
DELETE /api/v1/routines/{id} | deletes it |
POST /api/v1/routines/{id}/run-now | queues a run without an event. A paused routine answers 422 |
POST /api/v1/routines/{id}/rotate-token | gives the routine a new URL. The old one stops working |
POST /api/v1/routines/{id}/rotate-secret | gives it a new secret, returned once. The old one stops working |
GET /api/v1/routines/{id}/runs | the last 50 runs |
GET /api/v1/routines/blueprints | ready-made automations you can start from |
Validation failures are the usual 422 with errors. Refusals that come from the routine rules, such as reaching the limit of 10, are {"detail": "…"}.