1. Guides
  2. Integrations
  3. Webhooks

Webhooks

Integrations — Webhooks

Instead of polling, subscribe an HTTPS endpoint and Corteksa will push a signed HTTP POST to it when a record in an object is created, updated or deleted. An app registers its own subscriptions with its API key (crtk_live_…) or OAuth token (crtk_oauth_…), the same credential it uses to read and write records. One credential can hold many subscriptions.

Events today: record.created, record.updated and record.deleted. Call GET /api/v1/webhooks/events for the current list.

1. Subscribe

http
POST /api/v1/webhooks/subscriptionsAuthorization: Bearer crtk_live_…Content-Type: application/json{  "object_slug": "tasks",  "events": ["record.created", "record.updated"],  "target_url": "https://api.your-app.com/corteksa/webhook"}

name is optional. Without it, the subscription is named after the object and the target host.

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

json
{
  "status": 201,
  "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",
      "target_url": "https://api.your-app.com/corteksa/webhook",
      "object_slug": "tasks",
      "events": ["record.created", "record.updated"],
      "is_active": true,
      "created_at": "2026-07-13T10:00:00.000Z"
    }
  }
}
  • target_url must be HTTPS. Otherwise 422.
  • events must be names from the catalog (GET /api/v1/webhooks/events). An unknown name returns 400.
  • The credential must have read access to the object (scope read.<object_slug>), because a webhook only ever carries data the credential could already read. Otherwise 403.
  • An object_slug that matches no object returns 404.
  • 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, newest first
GET /api/v1/webhooks/subscriptions/:slugGet one
DELETE /api/v1/webhooks/subscriptions/:slugDelete one

A subscription belongs to the credential that created it: the API key, or the OAuth app for an OAuth token. Only that credential can list, read or delete it. The signing secret is never returned again after the create call. A user session (JWT) is rejected with 403, because these endpoints are for apps.

2. Receive a delivery

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

HeaderExampleMeaning
Content-Typeapplication/jsonThe body is JSON
X-Corteksa-Eventrecord.updatedThe event that fired
X-Corteksa-Delivery4021A number, new for every delivery attempt. A retry of the same event carries a different value, so it is not an idempotency key
X-Corteksa-Signaturet=1752400000,v1=9f86d0…Timestamp and HMAC signature — see §3

Body:

json
{
  "event": "record_updated",
  "event_type": "update",
  "timestamp": "2026-07-13T10:05:00.000Z",
  "object": "tasks",
  "record_id": 812,
  "record_slug": "5c9696e92c84",
  "changed_fields": {
    "email-k2x9p0zq": { "old": "sara@acme.com", "new": "sara.ali@acme.com" }
  },
  "full_record": { "email-k2x9p0zq": "sara.ali@acme.com", "slug": "5c9696e92c84" }
}

changed_fields and full_record are keyed by field slug. What they hold depends on the event: on record.updated, full_record holds only the fields the write sent, not the whole record. Use record_slug, not record_id, to fetch the record. Every event, its body and the values it can carry are listed in Webhook events.

Answer with any 2xx within 5 seconds. See §4 for what is retried.

3. Verify the signature

The X-Corteksa-Signature header is t=<unix-seconds>,v1=<hex>, where v1 is the lowercase hex HMAC-SHA256(signing_secret, "<t>.<raw-request-body>"). Use the whole signing secret as the key, including its whsec_ prefix. Recompute it over the raw body (do not re-serialize) and compare in constant time. Reject a t more than a few minutes from your clock to stop replays. Each retry is signed again with a new t, so this does not reject retries.

ts
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 delivery is tried up to 3 times. A timeout (5 seconds), a connection failure, a 5xx, a 408 or a 429 is retried with exponential backoff. Any other 4xx stops the retries.
  • Idempotency: nothing in the request stays the same across attempts — X-Corteksa-Delivery, t and timestamp all change. Make your handler safe to run twice, for example by treating a delivery as a signal and fetching the record by record_slug before you act. Deliveries can also arrive out of order.
  • No echo of your own writes: a change made by a request with your API key or OAuth token is not delivered to that credential's own subscriptions, so writing back in response to a delivery does not loop. Changes by other keys, apps and users still notify you, and so can work that runs later in the background, such as an automation your change triggers. See Webhook events for the exact rules.
  • Disconnecting: delete the subscription with DELETE /api/v1/webhooks/subscriptions/:slug, using the credential that created it.

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.
Examples< PreviousExamplesNext >

Powered by heyo-docs

On this page

1. SubscribeManage2. Receive a delivery3. Verify the signature4. Delivery, retries, and loops5. Security checklist