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.


EventDescription
skill.completedA skill execution finished successfully.
skill.failedA skill execution failed after all retries.
agent.errorAn agent-level error occurred (e.g., missing credentials, configuration issue).
agent.status_changedAn agent’s status transitioned (e.g., from active to paused).
conversation.createdA new conversation was initiated.
scheduled_job.completedA scheduled job execution completed.
scheduled_job.failedA 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 required string The agent whose events this webhook listens to.
url required string The HTTPS endpoint URL that will receive webhook payloads.
events required string[] An array of event types to subscribe to (e.g., ['skill.completed', 'agent.error']).
secret string A 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 required string The 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 required string The webhook identifier.

Request Body

ParameterTypeDescription
url string Updated HTTPS endpoint URL.
events string[] Updated list of subscribed event types.
secret string Updated shared secret for signature verification.
active boolean Enable 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 required string The 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:

HeaderDescription
Content-Typeapplication/json
x-horizon-signatureHMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
x-horizon-eventThe event type (e.g., skill.completed).
x-horizon-deliveryA 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:

AttemptDelay
1st retry30 seconds
2nd retry2 minutes
3rd retry10 minutes
4th retry1 hour
5th retry6 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.


StatusErrorDescription
400validation_errorInvalid request body, non-HTTPS URL, or unsupported event type.
401authentication_requiredMissing or invalid API key.
403insufficient_scopeAPI key lacks the required scope.
404not_foundThe specified webhook does not exist.
429rate_limit_exceededAPI key rate limit exceeded.