Skip to content

Webhooks

The Webhooks API lets you register HTTPS endpoints that receive real-time event notifications from Horizon. When an agent completes a skill, encounters an error, or triggers another subscribed event, Horizon sends an HTTP POST request to your configured URL with a JSON payload describing the event.


Event Description
skill.completed A skill execution finished successfully.
skill.failed A skill execution failed after all retries.
agent.error An agent-level error occurred (e.g., missing credentials, configuration issue).
agent.status_changed An agent’s status transitioned (e.g., from active to paused).
conversation.created A new conversation was initiated.
scheduled_job.completed A scheduled job execution completed.
scheduled_job.failed A scheduled job execution failed.

You can subscribe to one or more events per webhook.


POST/api/agent-webhooks

Register a new webhook endpoint to receive event notifications.

Requires authentication via x-api-key header.

Request Body

ParameterTypeDescription
agent_id requiredstringThe agent whose events this webhook listens to.
url requiredstringThe HTTPS endpoint URL that will receive webhook payloads.
events requiredstring[]An array of event types to subscribe to (e.g., ['skill.completed', 'agent.error']).
secretstringA shared secret used to generate HMAC-SHA256 signatures for payload verification. If omitted, Horizon generates one automatically.
curl
curl -X POST https://api.horizonplatform.ai/api/agent-webhooks \
-H "x-api-key: hz_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_001",
"url": "https://example.com/hooks/horizon",
"events": ["skill.completed", "skill.failed", "agent.error"],
"secret": "whsec_mysecretkey123"
}'
JavaScript
const response = await fetch(
'https://api.horizonplatform.ai/api/agent-webhooks',
{
method: 'POST',
headers: {
'x-api-key': 'hz_live_abc123def456',
'Content-Type': 'application/json',
},
body: JSON.stringify({
agent_id: 'agent_001',
url: 'https://example.com/hooks/horizon',
events: ['skill.completed', 'skill.failed', 'agent.error'],
secret: 'whsec_mysecretkey123',
}),
}
);
const webhook = await response.json();
console.log(webhook.id); // e.g., "wh_a1b2c3d4"
Python
import requests
response = requests.post(
'https://api.horizonplatform.ai/api/agent-webhooks',
headers={
'x-api-key': 'hz_live_abc123def456',
'Content-Type': 'application/json',
},
json={
'agent_id': 'agent_001',
'url': 'https://example.com/hooks/horizon',
'events': ['skill.completed', 'skill.failed', 'agent.error'],
'secret': 'whsec_mysecretkey123',
}
)
webhook = response.json()
print(webhook['id'])
// 201 Created
{
"id": "wh_a1b2c3d4",
"agent_id": "agent_001",
"url": "https://example.com/hooks/horizon",
"events": ["skill.completed", "skill.failed", "agent.error"],
"secret": "whsec_mysecretkey123",
"active": true,
"created_at": "2026-03-18T16:00:00Z",
"updated_at": "2026-03-18T16:00:00Z"
}

GET/api/agent-webhooks

List all webhooks for the authenticated organization.

Requires authentication via x-api-key header.

curl
curl -X GET https://api.horizonplatform.ai/api/agent-webhooks \
-H "x-api-key: hz_live_abc123def456"
JavaScript
const response = await fetch(
'https://api.horizonplatform.ai/api/agent-webhooks',
{ headers: { 'x-api-key': 'hz_live_abc123def456' } }
);
const webhooks = await response.json();
Python
import requests
response = requests.get(
'https://api.horizonplatform.ai/api/agent-webhooks',
headers={'x-api-key': 'hz_live_abc123def456'}
)
webhooks = response.json()

GET/api/agent-webhooks/:webhookId

Retrieve details of a single webhook by its identifier.

Requires authentication via x-api-key header.

Path Parameters

ParameterTypeDescription
webhookId requiredstringThe webhook identifier.
curl
curl -X GET https://api.horizonplatform.ai/api/agent-webhooks/wh_a1b2c3d4 \
-H "x-api-key: hz_live_abc123def456"
JavaScript
const response = await fetch(
'https://api.horizonplatform.ai/api/agent-webhooks/wh_a1b2c3d4',
{ headers: { 'x-api-key': 'hz_live_abc123def456' } }
);
const webhook = await response.json();
Python
import requests
response = requests.get(
'https://api.horizonplatform.ai/api/agent-webhooks/wh_a1b2c3d4',
headers={'x-api-key': 'hz_live_abc123def456'}
)
webhook = response.json()

PUT/api/agent-webhooks/:webhookId

Update a webhook's URL, subscribed events, or secret.

Requires authentication via x-api-key header.

Path Parameters

ParameterTypeDescription
webhookId requiredstringThe webhook identifier.

Request Body

ParameterTypeDescription
urlstringUpdated HTTPS endpoint URL.
eventsstring[]Updated list of subscribed event types.
secretstringUpdated shared secret for signature verification.
activebooleanEnable or disable the webhook without deleting it.
curl
curl -X PUT https://api.horizonplatform.ai/api/agent-webhooks/wh_a1b2c3d4 \
-H "x-api-key: hz_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"events": ["skill.completed", "skill.failed", "agent.error", "scheduled_job.failed"],
"active": true
}'
JavaScript
const response = await fetch(
'https://api.horizonplatform.ai/api/agent-webhooks/wh_a1b2c3d4',
{
method: 'PUT',
headers: {
'x-api-key': 'hz_live_abc123def456',
'Content-Type': 'application/json',
},
body: JSON.stringify({
events: ['skill.completed', 'skill.failed', 'agent.error', 'scheduled_job.failed'],
active: true,
}),
}
);
const updated = await response.json();
Python
import requests
response = requests.put(
'https://api.horizonplatform.ai/api/agent-webhooks/wh_a1b2c3d4',
headers={
'x-api-key': 'hz_live_abc123def456',
'Content-Type': 'application/json',
},
json={
'events': ['skill.completed', 'skill.failed', 'agent.error', 'scheduled_job.failed'],
'active': True,
}
)
updated = response.json()

DELETE/api/agent-webhooks/:webhookId

Permanently remove a webhook. No further events will be delivered to its URL.

Requires authentication via x-api-key header.

Path Parameters

ParameterTypeDescription
webhookId requiredstringThe webhook identifier.
curl
curl -X DELETE https://api.horizonplatform.ai/api/agent-webhooks/wh_a1b2c3d4 \
-H "x-api-key: hz_live_abc123def456"
JavaScript
await fetch(
'https://api.horizonplatform.ai/api/agent-webhooks/wh_a1b2c3d4',
{
method: 'DELETE',
headers: { 'x-api-key': 'hz_live_abc123def456' },
}
);
// 204 No Content on success
Python
import requests
response = requests.delete(
'https://api.horizonplatform.ai/api/agent-webhooks/wh_a1b2c3d4',
headers={'x-api-key': 'hz_live_abc123def456'}
)
# 204 No Content on success

When an event fires, Horizon sends an HTTP POST to your configured URL with the following structure:

{
"id": "evt_x9y8z7w6",
"event": "skill.completed",
"timestamp": "2026-03-18T16:45:00Z",
"agent_id": "agent_001",
"data": {
"job_id": "job_9f8e7d6c",
"skill_category": "quickbooks",
"skill_name": "profit-and-loss-report",
"skill_version": "v1.0",
"conversation_id": "conv_8f3a2b1c",
"duration_ms": 4230,
"result_summary": "P&L report generated for Q1 2026"
}
}

The request includes the following headers:

Header Description
Content-Type application/json
x-horizon-signature HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
x-horizon-event The event type (e.g., skill.completed).
x-horizon-delivery A unique delivery ID for idempotency tracking.

To verify that a webhook payload was sent by Horizon and has not been tampered with, compute an HMAC-SHA256 digest of the raw request body using your webhook secret, then compare it with the x-horizon-signature header value.

JavaScript
import crypto from 'node:crypto';
function verifyWebhookSignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your Express handler:
app.post('/hooks/horizon', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-horizon-signature'];
const isValid = verifyWebhookSignature(req.body, signature, 'whsec_mysecretkey123');
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Received event:', event.event);
res.status(200).send('OK');
});
Python
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = 'whsec_mysecretkey123'
@app.route('/hooks/horizon', methods=['POST'])
def handle_webhook():
signature = request.headers.get('x-horizon-signature', '')
raw_body = request.get_data()
expected = hmac.new(
WEBHOOK_SECRET.encode(),
raw_body,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
abort(401, 'Invalid signature')
event = request.get_json()
print(f"Received event: {event['event']}")
return 'OK', 200

Horizon expects your endpoint to respond with a 2xx status code within 10 seconds. If the delivery fails, Horizon retries up to 5 times with exponential backoff:

Attempt Delay
1st retry 30 seconds
2nd retry 2 minutes
3rd retry 10 minutes
4th retry 1 hour
5th retry 6 hours

After all retries are exhausted, the webhook is marked as failing. If 10 consecutive deliveries fail, the webhook is automatically deactivated (active: false). You can reactivate it via the Update endpoint after resolving the issue.


Status Error Description
400 validation_error Invalid request body, non-HTTPS URL, or unsupported event type.
401 authentication_required Missing or invalid API key.
403 insufficient_scope API key lacks the required scope.
404 not_found The specified webhook does not exist.
429 rate_limit_exceeded API key rate limit exceeded.