ShvIA

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":

bash
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"]
  }'
FieldNotes
namerequired, up to 120 characters
promptrequired, up to 8000 characters. The body of each event is added to it as data
triggerschedule (the default), webhook or email
delivery_targetsrequired, 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_aiwhether 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_enableddefaults to true. A routine that fails 3 times in a row is switched off
webhook_secretyour 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:

json
{
  "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_url carries 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 to PUT /api/v1/routines/{id}. Do not log those responses whole.
  • webhook_secret is shown once. Without a webhook_secret in your request, the server makes one and returns it. Send "webhook_secret": null to 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.

bash
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"
json
{ "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:

StatusBodyWhen
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.

RequestWhat it does
GET /api/v1/routineslists 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-nowqueues a run without an event. A paused routine answers 422
POST /api/v1/routines/{id}/rotate-tokengives the routine a new URL. The old one stops working
POST /api/v1/routines/{id}/rotate-secretgives it a new secret, returned once. The old one stops working
GET /api/v1/routines/{id}/runsthe last 50 runs
GET /api/v1/routines/blueprintsready-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": "…"}.