Outbound Webhooks
Outbound webhooks send HTTP POST notifications to your server when task events occur. They fire for all tasks in your organization, whether created via API, UI, or any other channel.
Events
| Event | Fires when |
|---|---|
task.created | A new task is created |
task.running | The agent starts executing |
task.completed | Task finishes successfully |
task.failed | Task fails |
task.canceled | Task is canceled by user |
Create Webhook
POST /v1/webhooks
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS endpoint URL |
events | string[] | Yes | Events to subscribe to |
description | string | No | Human-readable label (max 500 chars) |
secret | string | No | Pre-shared secret (max 500 chars). When set, included as X-Webhook-Secret header in every delivery. |
# Webhook without secret
curl -X POST https://api.rebyte.ai/v1/webhooks \
-H "API_KEY: rbk_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"events": ["task.completed", "task.failed"]
}'
# Webhook with pre-shared secret
curl -X POST https://api.rebyte.ai/v1/webhooks \
-H "API_KEY: rbk_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"events": ["task.completed", "task.failed"],
"secret": "my-shared-secret-value"
}'
Response (201):
{
"id": "880e8400-...",
"url": "https://your-server.com/webhook",
"events": ["task.completed", "task.failed"],
"description": null,
"hasSecret": true,
"isActive": true,
"createdAt": "2026-01-28T12:00:00.000Z",
"lastTriggeredAt": null,
"failureCount": 0
}
Behavior notes:
- Duplicate URLs: registering the same URL twice returns the existing webhook (idempotent)
- Limit: maximum 3 webhooks per organization. The 4th returns
limit_exceeded. - Secret storage: the secret value is never returned in any response. Only
hasSecret: true/falseis exposed.
List Webhooks
GET /v1/webhooks
Returns all webhooks for your organization with status info (lastTriggeredAt, failureCount, isActive). Secrets are never exposed.
Get Webhook
GET /v1/webhooks/:id
Delete Webhook
DELETE /v1/webhooks/:id
Returns 204 No Content. Deleted webhooks immediately stop receiving deliveries.
Delivery
When a task event matches a webhook's subscribed events, Rebyte sends an HTTP POST to the webhook URL.
Payload:
{
"event": "task.completed",
"taskId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": 1706443200,
"data": {
"status": "succeeded",
"taskUrl": "https://app.rebyte.ai/run/550e8400-...",
"result": "Created CSV file with 10 Hacker News posts..."
}
}
status— prompt status:pending(task.created),running(task.running),succeeded(task.completed),failed(task.failed),canceled(task.canceled)taskUrl— direct link to the task in the Rebyte UI (always present)result— final AI output text (present on terminal events:task.completed,task.failed,task.canceled; absent ontask.createdandtask.running)
Call GET /v1/tasks/:id for full task details including prompt history.
Delivery headers:
| Header | Always present | Description |
|---|---|---|
Content-Type | Yes | application/json |
X-Webhook-Signature | Yes | Base64-encoded RSA-SHA256 signature |
X-Webhook-Timestamp | Yes | Unix timestamp (seconds) |
X-Webhook-Secret | Only if secret configured | The pre-shared secret value you set on creation |
Timeout: deliveries time out after 10 seconds.
Failure handling:
- Delivery is fire-and-forget — no automatic retries on failure
- Non-2xx responses increment
failureCount - After 10 consecutive failures, the webhook is automatically disabled (
isActive: false) - Successful deliveries reset
failureCountto 0
Signature Verification
Every delivery is signed with your organization's RSA-2048 key pair, regardless of whether a pre-shared secret is configured.
How it works:
- Rebyte concatenates
{timestamp}.{body}(e.g.,1706443200.{"event":"task.completed",...}) - Signs the string with RSA-SHA256 using your org's private key
- Sends the base64-encoded signature in
X-Webhook-Signature
Get your public key: retrieve it from the API Playground.
Verification example (Node.js):
const crypto = require('crypto');
function verifyWebhook(rawBody, timestamp, signature, publicKey) {
const payload = `${timestamp}.${rawBody}`;
const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(payload);
return verifier.verify(publicKey, signature, 'base64');
}
// In your webhook handler:
app.post('/webhook', (req, res) => {
const rawBody = req.body; // must be raw string, not parsed JSON
const timestamp = req.headers['x-webhook-timestamp'];
const signature = req.headers['x-webhook-signature'];
if (!verifyWebhook(rawBody, timestamp, signature, PUBLIC_KEY)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
console.log(`Task ${event.taskId}: ${event.event}`);
res.status(200).send('OK');
});
Verification example (Python):
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import base64
def verify_webhook(raw_body: str, timestamp: str, signature: str, public_key_pem: str) -> bool:
public_key = serialization.load_pem_public_key(public_key_pem.encode())
payload = f"{timestamp}.{raw_body}".encode()
try:
public_key.verify(
base64.b64decode(signature),
payload,
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception:
return False
Two-Layer Security
Webhooks support two independent verification methods that can be used together:
| Method | Header | How it works | Use case |
|---|---|---|---|
| RSA signature | X-Webhook-Signature | Cryptographic proof the payload came from Rebyte | Tamper-proof verification |
| Pre-shared secret | X-Webhook-Secret | Static string you set on creation, echoed back in every delivery | Simple shared-secret check |
RSA signatures are always present. The pre-shared secret is optional — set it when creating the webhook if you want a simpler verification path.