Skip to main content

Webhooks

Webhooks allow your application to receive real-time notifications when events occur in CODITECT.

Overview

When an event occurs, CODITECT sends an HTTP POST request to your configured endpoint with details about the event.

CODITECT Event → Webhook Endpoint → Your Server

Setting Up Webhooks

Create Webhook Endpoint

POST /v1/webhooks

curl -X POST https://api.coditect.ai/v1/webhooks \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/coditect",
"events": ["license.seat.acquired", "license.seat.released"],
"description": "Production webhook"
}'

Request Body:

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint URL
eventsarrayYesEvents to subscribe to
descriptionstringNoWebhook description
secretstringNoCustom signing secret

Response:

{
"data": {
"id": "wh_abc123",
"url": "https://yourapp.com/webhooks/coditect",
"events": ["license.seat.acquired", "license.seat.released"],
"secret": "whsec_xxx...",
"status": "active",
"created_at": "2026-01-09T10:00:00Z"
}
}
Save Your Secret

The webhook signing secret is only shown once. Save it securely to verify webhook signatures.

List Webhooks

GET /v1/webhooks

curl https://api.coditect.ai/v1/webhooks \
-H "Authorization: Bearer YOUR_TOKEN"

Delete Webhook

DELETE /v1/webhooks/{id}

curl -X DELETE https://api.coditect.ai/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer YOUR_TOKEN"

Event Types

License Events

EventDescription
license.seat.acquiredUser acquired a license seat
license.seat.releasedUser released a license seat
license.seat.timeoutSeat released due to inactivity
license.seats.exhaustedAll seats are in use
license.expiringLicense expires within 7 days
license.expiredLicense has expired

Subscription Events

EventDescription
subscription.createdNew subscription started
subscription.updatedSubscription changed
subscription.canceledSubscription canceled
subscription.expiredSubscription has ended

Invoice Events

EventDescription
invoice.createdInvoice generated
invoice.paidInvoice payment successful
invoice.payment_failedPayment attempt failed

Organization Events

EventDescription
org.member.invitedMember invitation sent
org.member.joinedMember accepted invitation
org.member.removedMember was removed
org.member.role_changedMember role updated

Webhook Payload

All webhook payloads follow this structure:

{
"id": "evt_abc123",
"type": "license.seat.acquired",
"created": "2026-01-09T10:30:00Z",
"data": {
"object": {
// Event-specific data
}
},
"organization_id": "org_xyz789"
}

Example: license.seat.acquired

{
"id": "evt_abc123",
"type": "license.seat.acquired",
"created": "2026-01-09T10:30:00Z",
"data": {
"object": {
"session_id": "sess_xyz789",
"license_id": "lic_abc123",
"user_id": "usr_def456",
"hardware_id": "hw_ghi789",
"active_seats": 4,
"max_seats": 10
}
}
}

Example: subscription.canceled

{
"id": "evt_def456",
"type": "subscription.canceled",
"created": "2026-01-09T11:00:00Z",
"data": {
"object": {
"subscription_id": "sub_abc123",
"plan_id": "plan_pro",
"cancel_at_period_end": true,
"current_period_end": "2026-02-01T00:00:00Z",
"canceled_by": "usr_abc123",
"reason": "Switching to annual plan"
}
}
}

Verifying Signatures

Always verify webhook signatures to ensure requests are from CODITECT.

Signature Header

X-Coditect-Signature: t=1704794400,v1=abc123...

Verification Process

  1. Extract timestamp and signature from header
  2. Construct signed payload: {timestamp}.{request_body}
  3. Compute HMAC-SHA256 with your webhook secret
  4. Compare with provided signature
  5. Verify timestamp is within 5 minutes

Python Example

import hmac
import hashlib
import time

def verify_webhook(payload: bytes, signature_header: str, secret: str) -> bool:
"""Verify CODITECT webhook signature."""
# Parse header
parts = dict(item.split("=") for item in signature_header.split(","))
timestamp = parts.get("t")
signature = parts.get("v1")

# Check timestamp (prevent replay attacks)
if abs(time.time() - int(timestamp)) > 300: # 5 minutes
return False

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

# Compare signatures (constant-time)
return hmac.compare_digest(expected, signature)

# Usage in Flask
from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_xxx..."

@app.route("/webhooks/coditect", methods=["POST"])
def handle_webhook():
payload = request.get_data()
signature = request.headers.get("X-Coditect-Signature")

if not verify_webhook(payload, signature, WEBHOOK_SECRET):
return "Invalid signature", 400

event = request.get_json()
event_type = event["type"]

if event_type == "license.seat.acquired":
handle_seat_acquired(event["data"]["object"])
elif event_type == "subscription.canceled":
handle_subscription_canceled(event["data"]["object"])

return "OK", 200

JavaScript Example

const crypto = require('crypto');
const express = require('express');

const WEBHOOK_SECRET = 'whsec_xxx...';

function verifyWebhook(payload, signatureHeader) {
const parts = Object.fromEntries(
signatureHeader.split(',').map(p => p.split('='))
);
const timestamp = parts.t;
const signature = parts.v1;

// Check timestamp
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
return false;
}

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

return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}

const app = express();
app.use(express.raw({ type: 'application/json' }));

app.post('/webhooks/coditect', (req, res) => {
const signature = req.headers['x-coditect-signature'];

if (!verifyWebhook(req.body.toString(), signature)) {
return res.status(400).send('Invalid signature');
}

const event = JSON.parse(req.body);
console.log('Received event:', event.type);

res.status(200).send('OK');
});

Best Practices

1. Return Quickly

Respond with 200 OK immediately, then process asynchronously:

@app.route("/webhooks/coditect", methods=["POST"])
def handle_webhook():
event = request.get_json()

# Queue for async processing
queue.enqueue(process_webhook_event, event)

return "OK", 200 # Return immediately

2. Handle Retries

CODITECT retries failed webhooks:

  • 1st retry: 1 minute
  • 2nd retry: 5 minutes
  • 3rd retry: 30 minutes
  • 4th retry: 2 hours
  • 5th retry: 24 hours

Use idempotency keys (event.id) to handle duplicates.

3. Monitor Deliveries

Check delivery status in the dashboard:

  • Navigate to Settings → Webhooks → Delivery History
  • View recent events, response codes, and retry attempts

4. Test with CLI

coditect webhooks test --event license.seat.acquired

This sends a test event to your endpoint.

Troubleshooting

Webhook Not Receiving Events

  1. Verify URL is publicly accessible
  2. Check firewall allows CODITECT IPs
  3. Ensure SSL certificate is valid
  4. Verify endpoint returns 2xx status

Signature Verification Failing

  1. Use raw request body (not parsed JSON)
  2. Ensure secret matches dashboard value
  3. Check server clock is synchronized
  4. Verify HMAC algorithm is SHA-256

Events Arriving Late

  • Events are sent within seconds
  • Check for processing delays on your end
  • Consider async processing for reliability