Corteksa
GuidesIntegrations

Webhooks

Integrations — Webhooks

Instead of polling, subscribe an HTTPS endpoint and Corteksa will push a signed HTTP POST to it whenever a record changes. An app registers its own subscriptions with its API key (crtk_live_…) — the same key it uses to read and write records. One key can hold many subscriptions.

Scope today: the objects (records) module — record.created, record.updated, record.deleted. More modules (messaging, email, …) are added over time; call GET /webhooks/events for the live catalog.

1. Subscribe

POST /api/v1/webhooks/subscriptions
Authorization: Bearer crtk_live_…
Content-Type: application/json

{
  "object_slug": "tasks",
  "events": ["record.created", "record.updated"],
  "target_url": "https://api.your-app.com/corteksa/webhook"
}

201 response — the signing secret is shown exactly once. Store it now.

{
  "message": "Webhook subscription created. Copy the signing secret now — it will not be shown again.",
  "data": {
    "signing_secret": "whsec_1a2b3c…",
    "subscription": {
      "slug": "a1b2c3d4e5f6",
      "name": "Tasks → api.your-app.com",
      "object_slug": "tasks",
      "events": ["record.created", "record.updated"],
      "target_url": "https://api.your-app.com/corteksa/webhook",
      "is_active": true,
      "created_at": "2026-07-13T10:00:00.000Z"
    }
  }
}
  • target_url must be HTTPS.
  • events must be names from the catalog (GET /api/v1/webhooks/events).
  • The key must have read access to the object (scope read.<object_slug>) — a webhook only ever carries data the key could already read. Otherwise 403.
  • Subscriptions are addressed by slug, never a numeric id.

Manage

Method & pathDoes
GET /api/v1/webhooks/eventsList subscribable event names
GET /api/v1/webhooks/subscriptionsList your subscriptions
GET /api/v1/webhooks/subscriptions/:slugGet one
DELETE /api/v1/webhooks/subscriptions/:slugDelete one

A subscription is owned by the credential that created it — you only ever see and manage your own. Either credential type works: an API key (crtk_live_…) or an OAuth token (crtk_oauth_…, owned by the OAuth app installation). A human (JWT) token is rejected (403) — these endpoints are for apps.

2. Receive a delivery

Corteksa sends a POST with a JSON body and these headers:

HeaderExampleMeaning
X-Corteksa-Eventrecord.updatedThe event that fired
X-Corteksa-Delivery4021Unique per delivery attempt (idempotency key)
X-Corteksa-Signaturet=1752400000,v1=9f86d0…HMAC — see below

Body:

{
  "event": "record_updated",
  "event_type": "update",
  "timestamp": "2026-07-13T10:05:00.000Z",
  "object": "tasks",
  "record_id": 812,
  "record_slug": "tsk_9f2c",
  "changed_fields": { "status": { "old": "open", "new": "done" } },
  "full_record": { "slug": "tsk_9f2c", "title": "Ship it", "status": "done" }
}

full_record and changed_fields are keyed by field slug. Respond 2xx quickly; anything else (or a timeout) is retried (see §4).

3. Verify the signature

The X-Corteksa-Signature header is t=<unix-seconds>,v1=<hex>, where v1 is HMAC-SHA256(signing_secret, "<t>.<raw-request-body>"). Recompute it over the raw body (do not re-serialize) and compare in constant time. Reject if t is older than, say, 5 minutes to stop replays.

import { createHmac, timingSafeEqual } from 'crypto';

function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // replay guard
  const expected = createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? '');
  return a.length === b.length && timingSafeEqual(a, b);
}

4. Delivery, retries, and loops

  • Retries: a failed delivery (non-2xx or timeout) is retried with exponential backoff. Make your handler idempotent using X-Corteksa-Delivery.
  • No echo of your own writes: an event caused by a write your key made is not delivered back to your subscription. So a two-way sync won't loop — your writes into Corteksa never bounce back to you. (Writes by other keys/users still notify you, as expected.)
  • Disconnecting: delete the subscription, or revoke the API key — either stops deliveries.

5. Security checklist

  • Treat signing_secret like a password; store it server-side.
  • Always verify X-Corteksa-Signature before trusting a payload.
  • Enforce the timestamp replay window.
  • Only accept HTTPS callbacks.

On this page