Skip to main content

Webhooks Endpoint

The Webhooks API allows you to receive real-time notifications when events occur in CICosts.

Webhook Events

EventDescription
workflow.completedA workflow run has completed
alert.triggeredAn alert has been triggered
alert.resolvedAn alert has been resolved
cost.thresholdA cost threshold has been reached
export.readyAn export file is ready for download

List Webhooks

Get all configured webhooks.

GET /v1/webhooks

Example Response

{
"data": {
"webhooks": [
{
"id": "wh_abc123",
"url": "https://example.com/webhooks/cicosts",
"events": ["workflow.completed", "alert.triggered"],
"status": "active",
"secret_prefix": "whsec_abc...",
"last_delivery_at": "2024-01-15T11:30:00Z",
"last_delivery_status": "success",
"created_at": "2023-12-01T00:00:00Z"
}
]
}
}

Create Webhook

Create a new webhook endpoint.

POST /v1/webhooks

Request Body

{
"url": "https://example.com/webhooks/cicosts",
"events": ["workflow.completed", "alert.triggered"],
"secret": "optional_shared_secret"
}

Example Response

{
"data": {
"id": "wh_def456",
"url": "https://example.com/webhooks/cicosts",
"events": ["workflow.completed", "alert.triggered"],
"status": "active",
"secret": "whsec_xxxxxxxxxxxxxxxxxxx",
"created_at": "2024-01-15T12:00:00Z"
}
}
Secret shown once

The webhook secret is only returned when creating the webhook. Store it securely.

Update Webhook

Update a webhook configuration.

PUT /v1/webhooks/:id

Request Body

{
"url": "https://example.com/webhooks/new-endpoint",
"events": ["alert.triggered", "alert.resolved"],
"status": "paused"
}

Delete Webhook

Delete a webhook.

DELETE /v1/webhooks/:id

Webhook Payloads

workflow.completed

Sent when a workflow run completes.

{
"event": "workflow.completed",
"timestamp": "2024-01-15T11:30:00Z",
"data": {
"run": {
"id": "run_xyz789",
"workflow_id": "wf_abc123",
"workflow_name": "ci.yml",
"repository": "api-service",
"organization": "my-company",
"status": "success",
"duration_minutes": 12.5,
"cost": 3.45,
"started_at": "2024-01-15T11:15:00Z",
"completed_at": "2024-01-15T11:27:30Z"
}
}
}

alert.triggered

Sent when an alert is triggered.

{
"event": "alert.triggered",
"timestamp": "2024-01-15T08:00:00Z",
"data": {
"alert": {
"id": "alert_abc123",
"name": "Monthly Budget Alert",
"type": "threshold"
},
"trigger": {
"current_value": 2015.67,
"threshold_value": 2000,
"metric": "monthly_cost"
}
}
}

alert.resolved

Sent when an alert condition is no longer met.

{
"event": "alert.resolved",
"timestamp": "2024-01-16T08:00:00Z",
"data": {
"alert": {
"id": "alert_abc123",
"name": "Monthly Budget Alert"
},
"resolution": {
"triggered_at": "2024-01-15T08:00:00Z",
"resolved_at": "2024-01-16T08:00:00Z",
"duration_hours": 24
}
}
}

cost.threshold

Sent when a cost threshold is reached (50%, 75%, 90%, 100% of budget).

{
"event": "cost.threshold",
"timestamp": "2024-01-15T10:00:00Z",
"data": {
"threshold": {
"percentage": 75,
"budget": 2000,
"current_spend": 1500
},
"scope": {
"type": "organization",
"org": "my-company"
}
}
}

Verifying Webhooks

Webhook requests include a signature header for verification.

Signature Header

X-CICosts-Signature: sha256=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Verification (Node.js)

const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

return `sha256=${expectedSignature}` === signature;
}

// Express middleware
app.post('/webhooks/cicosts', (req, res) => {
const signature = req.headers['x-cicosts-signature'];
const payload = JSON.stringify(req.body);

if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}

// Process the webhook
console.log('Received:', req.body.event);
res.status(200).send('OK');
});

Verification (Python)

import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return f"sha256={expected}" == signature

Webhook Delivery

Retry Policy

Failed deliveries are retried:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours

After 5 failures, the webhook is marked as failing.

Response Requirements

Your endpoint must:

  • Respond within 30 seconds
  • Return a 2xx status code
  • Handle duplicate deliveries (use event IDs)

Debugging

View delivery history:

GET /v1/webhooks/:id/deliveries
{
"data": {
"deliveries": [
{
"id": "del_abc123",
"event": "workflow.completed",
"status": "success",
"response_code": 200,
"response_time_ms": 234,
"delivered_at": "2024-01-15T11:30:00Z"
}
]
}
}

See also: Integrations →