Webhooks
Configure webhook notifications
Webhooks
Webhooks let you receive HTTP callbacks when key events occur in your workflows. Stevora signs every webhook delivery with HMAC-SHA256 so you can verify authenticity.
Create a Webhook
POST /v1/webhooks
Registers a new webhook endpoint. Stevora generates an HMAC signing secret and returns it in the response. Store this secret securely -- it is only shown once.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The HTTPS URL to receive webhook payloads (must be a valid URL) |
events | string[] | Yes | Array of event types to subscribe to (at least one) |
Available Events
| Event | Trigger |
|---|---|
workflow.completed | A workflow run finished successfully |
workflow.failed | A workflow run failed after exhausting retries |
workflow.cancelled | A workflow run was cancelled |
approval.requested | An approval step is waiting for a decision |
approval.decided | An approval decision was submitted |
Example Request
curl -X POST https://api.stevora.dev/v1/webhooks \
-H "x-api-key: stv_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/stevora",
"events": ["workflow.completed", "workflow.failed", "approval.requested"]
}'Response 201 Created
{
"success": true,
"data": {
"id": "wh_abc123",
"workspaceId": "ws_xyz789",
"url": "https://your-app.com/webhooks/stevora",
"events": ["workflow.completed", "workflow.failed", "approval.requested"],
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0...",
"isActive": true,
"createdAt": "2026-04-02T12:00:00.000Z"
}
}The secret field is only included in the creation response. Store it securely for signature verification.
Error Responses
| Status | Code | Cause |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid URL, empty events array, or unrecognized event type |
| 401 | AUTH_ERROR | Missing or invalid API key |
List Webhooks
GET /v1/webhooks
Returns all webhooks registered in your workspace.
Example Request
curl https://api.stevora.dev/v1/webhooks \
-H "x-api-key: stv_your_api_key"Response 200 OK
{
"success": true,
"data": [
{
"id": "wh_abc123",
"workspaceId": "ws_xyz789",
"url": "https://your-app.com/webhooks/stevora",
"events": ["workflow.completed", "workflow.failed", "approval.requested"],
"isActive": true,
"createdAt": "2026-04-02T12:00:00.000Z"
}
]
}The secret is never included in list responses.
Error Responses
| Status | Code | Cause |
|---|---|---|
| 401 | AUTH_ERROR | Missing or invalid API key |
Delete a Webhook
DELETE /v1/webhooks/:id
Removes a webhook. Stevora will stop sending deliveries to the URL immediately.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The webhook ID |
Example Request
curl -X DELETE https://api.stevora.dev/v1/webhooks/wh_abc123 \
-H "x-api-key: stv_your_api_key"Response 200 OK
{
"success": true,
"data": {
"id": "wh_abc123",
"deleted": true
}
}Error Responses
| Status | Code | Cause |
|---|---|---|
| 401 | AUTH_ERROR | Missing or invalid API key |
| 404 | NOT_FOUND | Webhook not found or belongs to another workspace |
Webhook Payload Format
Every webhook delivery is an HTTP POST with a JSON body:
{
"event": "workflow.completed",
"workflowRunId": "wfrun_def456",
"workspaceId": "ws_xyz789",
"data": {
"status": "COMPLETED",
"output": { "result": "success" },
"completedAt": "2026-04-02T12:05:00.000Z"
},
"timestamp": "2026-04-02T12:05:00.123Z"
}Delivery Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Webhook-Signature | HMAC-SHA256 hex digest of the request body |
X-Webhook-Event | The event type (e.g., workflow.completed) |
X-Webhook-Id | The webhook registration ID |
Signature Verification
Stevora signs the raw JSON body using HMAC-SHA256 with the webhook's secret. To verify a delivery:
- Read the raw request body (before any JSON parsing)
- Compute the HMAC-SHA256 of the body using your stored secret
- Compare the result with the
X-Webhook-Signatureheader
Node.js Example
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyWebhookSignature(body, signature, secret) {
const expected = createHmac('sha256', secret)
.update(body)
.digest('hex');
return timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
);
}
// In your Express/Fastify handler:
const isValid = verifyWebhookSignature(
rawBody,
req.headers['x-webhook-signature'],
process.env.STEVORA_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}Python Example
import hmac
import hashlib
def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)Retry Behavior
Stevora retries failed webhook deliveries up to 3 times with increasing backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 5 seconds |
| 3 | 30 seconds |
A delivery is considered failed if your endpoint returns a non-2xx status code, times out (10 second limit), or is unreachable.