Skip to main content

Webhooks

FieldValue
Document IDASCEND-NOTIF-004
Version1.0.0
Last UpdatedDecember 19, 2025
AuthorAscend Engineering Team
PublisherOW-KAI Technologies Inc.
ClassificationEnterprise Client Documentation
ComplianceSOC 2 CC6.1/CC6.2, PCI-DSS 7.1/8.3, HIPAA 164.312, NIST 800-53 AC-2/SI-4

Reading Time: 10 minutes | Skill Level: Advanced

Overview

ASCEND webhooks provide real-time event delivery to your systems with enterprise security features including HMAC-SHA256 signatures, automatic retries, and dead letter queue for failed deliveries.

Webhook Security

+---------------------------------------------------------------------------------+
| WEBHOOK SECURITY MODEL |
+---------------------------------------------------------------------------------+
| |
| 1. EVENT GENERATION |
| +-------------------------------------------------------------------------+ |
| | Event Created -> JSON Payload -> Add Metadata (timestamp, event_id) | |
| +-------------------------------------------------------------------------+ |
| | |
| v |
| 2. SIGNATURE GENERATION |
| +-------------------------------------------------------------------------+ |
| | HMAC-SHA256(webhook_secret, timestamp + "." + payload) = signature | |
| +-------------------------------------------------------------------------+ |
| | |
| v |
| 3. DELIVERY |
| +-------------------------------------------------------------------------+ |
| | POST https://your-endpoint.com | |
| | Headers: | |
| | X-ASCEND-Signature: sha256=abc123... | |
| | X-ASCEND-Timestamp: 1702656000 | |
| | X-ASCEND-Event-Type: action.submitted | |
| | X-ASCEND-Delivery-ID: del_xyz789 | |
| +-------------------------------------------------------------------------+ |
| | |
| v |
| 4. VERIFICATION (Your Server) |
| +-------------------------------------------------------------------------+ |
| | expected = HMAC-SHA256(secret, timestamp + "." + body) | |
| | if expected == received_signature: VALID | |
| +-------------------------------------------------------------------------+ |
| |
+---------------------------------------------------------------------------------+

Create Subscription

curl -X POST "https://pilot.owkai.app/api/webhooks" \
-H "Authorization: Bearer <admin_jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Webhook",
"target_url": "https://your-app.com/webhooks/ascend",
"event_types": [
"action.submitted",
"action.approved",
"action.denied",
"security.alert"
],
"description": "Main production webhook for governance events",
"rate_limit_per_minute": 100,
"custom_headers": {
"X-Custom-Header": "your-value"
}
}'

Response:

{
"id": 1,
"subscription_id": "sub_abc123",
"name": "Production Webhook",
"target_url": "https://your-app.com/webhooks/ascend",
"event_types": ["action.submitted", "action.approved", "action.denied", "security.alert"],
"is_active": true,
"is_verified": false,
"rate_limit_per_minute": 100,
"secret_key": "whsec_live_a1b2c3d4e5f6g7h8i9j0...",
"created_at": "2025-12-15T10:30:00Z"
}
Secret Key

The secret_key is only returned once at creation time. Store it securely - it cannot be retrieved again.

Event Types

Available Events

# List all available webhook events
curl "https://pilot.owkai.app/api/webhooks/events" \
-H "Authorization: Bearer <jwt_token>"

Response:

{
"events": [
{
"type": "action.submitted",
"description": "Action submitted for governance review",
"payload_schema": "ActionSubmittedPayload",
"category": "Actions"
},
{
"type": "action.approved",
"description": "Action approved (auto or manual)",
"payload_schema": "ActionApprovedPayload",
"category": "Actions"
},
{
"type": "action.denied",
"description": "Action denied",
"payload_schema": "ActionDeniedPayload",
"category": "Actions"
},
{
"type": "security.alert",
"description": "Security alert triggered",
"payload_schema": "SecurityAlertPayload",
"category": "Security"
},
{
"type": "agent.killed",
"description": "Agent kill-switch activated",
"payload_schema": "AgentKilledPayload",
"category": "Agents"
}
],
"total": 12
}

Payload Structure

Action Submitted

{
"event_type": "action.submitted",
"event_id": "evt_abc123",
"timestamp": "2025-12-15T10:30:00Z",
"data": {
"action_id": "act_xyz789",
"agent_id": "trading-bot-001",
"action_type": "trade_execution",
"description": "Execute buy order for AAPL",
"risk_score": 65,
"risk_level": "medium",
"status": "pending_approval",
"parameters": {
"symbol": "AAPL",
"quantity": 100,
"price": 150.00
},
"submitted_at": "2025-12-15T10:30:00Z"
},
"organization_id": 1
}

Action Approved

{
"event_type": "action.approved",
"event_id": "evt_def456",
"timestamp": "2025-12-15T10:35:00Z",
"data": {
"action_id": "act_xyz789",
"agent_id": "trading-bot-001",
"approval_type": "manual",
"approved_by": "admin@company.com",
"approval_reason": "Within trading limits",
"risk_score": 65,
"approved_at": "2025-12-15T10:35:00Z"
},
"organization_id": 1
}

Security Alert

{
"event_type": "security.alert",
"event_id": "evt_sec789",
"timestamp": "2025-12-15T10:40:00Z",
"data": {
"alert_id": "alert_abc123",
"alert_type": "anomaly_detected",
"severity": "high",
"description": "Unusual action pattern detected for agent trading-bot-001",
"affected_agent": "trading-bot-001",
"risk_factors": [
"action_frequency_spike",
"unusual_parameters"
],
"recommended_action": "Review agent activity",
"triggered_at": "2025-12-15T10:40:00Z"
},
"organization_id": 1
}

Signature Verification

Python Example

import hmac
import hashlib

def verify_webhook_signature(
payload: bytes,
signature: str,
timestamp: str,
secret: str
) -> bool:
"""Verify ASCEND webhook signature."""

# Check timestamp is recent (within 5 minutes)
import time
current_time = int(time.time())
if abs(current_time - int(timestamp)) > 300:
return False # Replay attack protection

# Compute expected signature
signed_payload = f"{timestamp}.{payload.decode('utf-8')}"
expected_signature = hmac.new(
secret.encode('utf-8'),
signed_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()

# Constant-time comparison
return hmac.compare_digest(
f"sha256={expected_signature}",
signature
)

# Usage in Flask
@app.route('/webhooks/ascend', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-ASCEND-Signature')
timestamp = request.headers.get('X-ASCEND-Timestamp')

if not verify_webhook_signature(
request.data,
signature,
timestamp,
WEBHOOK_SECRET
):
return {'error': 'Invalid signature'}, 401

event = request.json
# Process event...
return {'status': 'received'}, 200

Node.js Example

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, timestamp, secret) {
// Check timestamp is recent
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
return false;
}

// Compute expected signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');

// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expectedSignature}`),
Buffer.from(signature)
);
}

// Express middleware
app.post('/webhooks/ascend', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-ascend-signature'];
const timestamp = req.headers['x-ascend-timestamp'];

if (!verifyWebhookSignature(req.body.toString(), signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).json({error: 'Invalid signature'});
}

const event = JSON.parse(req.body);
// Process event...
res.json({status: 'received'});
});

Test Webhook

curl -X POST "https://pilot.owkai.app/api/webhooks/1/test" \
-H "Authorization: Bearer <admin_jwt>" \
-H "Content-Type: application/json" \
-d '{
"event_type": "action.submitted",
"custom_payload": {
"test": true,
"message": "Test webhook delivery"
}
}'

Response:

{
"success": true,
"delivery_id": 123,
"response_status": 200,
"response_time_ms": 245,
"error_message": null,
"signature_header": "sha256=a1b2c3d4..."
}

Delivery Management

View Delivery History

curl "https://pilot.owkai.app/api/webhooks/1/deliveries?limit=50" \
-H "Authorization: Bearer <admin_jwt>"

Response:

{
"deliveries": [
{
"id": 123,
"event_id": "evt_abc123",
"event_type": "action.submitted",
"delivery_status": "success",
"attempt_count": 1,
"response_status": 200,
"response_time_ms": 245,
"error_message": null,
"created_at": "2025-12-15T10:30:00Z",
"delivered_at": "2025-12-15T10:30:00Z"
}
]
}

Retry Configuration

# Default retry configuration
RETRY_CONFIG = {
"max_attempts": 5,
"backoff_type": "exponential",
"initial_delay_seconds": 1,
"max_delay_seconds": 60,
"retry_on_status_codes": [408, 429, 500, 502, 503, 504]
}

Dead Letter Queue

View DLQ Entries

curl "https://pilot.owkai.app/api/webhooks/dlq/entries?resolved=false&limit=50" \
-H "Authorization: Bearer <admin_jwt>"

Response:

{
"entries": [
{
"id": 1,
"event_id": "evt_failed123",
"event_type": "action.submitted",
"failure_reason": "Connection timeout after 30s",
"total_attempts": 5,
"last_attempt_at": "2025-12-15T10:35:00Z",
"resolved": false,
"created_at": "2025-12-15T10:30:00Z"
}
]
}

Retry DLQ Entry

curl -X POST "https://pilot.owkai.app/api/webhooks/dlq/1/retry" \
-H "Authorization: Bearer <admin_jwt>"

Resolve DLQ Entry

curl -X POST "https://pilot.owkai.app/api/webhooks/dlq/1/resolve" \
-H "Authorization: Bearer <admin_jwt>" \
-H "Content-Type: application/json" \
-d '{
"notes": "Manually processed via ServiceNow INC0012345"
}'

Secret Rotation

curl -X POST "https://pilot.owkai.app/api/webhooks/1/rotate-secret" \
-H "Authorization: Bearer <admin_jwt>"

Response:

{
"subscription_id": 1,
"new_secret": "whsec_live_new_secret_key...",
"message": "Secret rotated successfully. Update your webhook receiver with the new secret."
}

Best Practices

1. Verify Signatures

# Always verify signatures before processing
if not verify_signature(request):
return 401

2. Respond Quickly

# Acknowledge receipt quickly, process async
@app.route('/webhooks/ascend', methods=['POST'])
def handle_webhook():
# Verify signature
# Queue for async processing
queue.enqueue(process_event, request.json)
return {'status': 'received'}, 200

3. Handle Duplicates

# Use event_id for idempotency
processed_events = redis_client.get('processed_events')
if event['event_id'] in processed_events:
return {'status': 'already_processed'}, 200

4. Monitor Delivery Health

# Alert on high failure rates
if failure_rate > 0.05: # 5%
alert("Webhook delivery degraded")

Next Steps


Document Version: 1.0.0 | Last Updated: December 2025