Webhooks

Signed events for orders, refunds, check-ins and pages.

Konecto sends signed POST requests to your endpoints and retries with backoff for about 72 hours (1 min, 5 min, 15 min, 1 h, 3 h, 6 h, 12 h, then daily). Answer with any 2xx within 10 seconds.

EventWhendata
order.paidA checkout completed and tickets were issuedorder_id, event_id, total_minor, currency, tickets
order.refundedAn order was fully or partly refundedorder_id, amount_minor, currency
attendee.registeredA guest was added by importevent_id, email, source
ticket.checked_inA ticket was scanned at the doorticket_id, event_id, device_id
event.publishedAn event was publishedevent_id, public_url
event.updatedEvent details changedevent_id, changed[]
event_page.publishedThe event page was publishedevent_id
module.enabledA module was enabled for an eventevent_id, module
challenge.completedAn attendee completed a challengeevent_id, challenge_id
booth.scannedAn exhibitor scanned an attendee at a boothevent_id, booth_id

Payload

{
  "id": "whd_9f2c…",
  "type": "order.paid",
  "created": "2026-11-14T18:02:11.412Z",
  "data": { "order_id": "ord_91x…", "event_id": "evt_2Lm…", "total_minor": 4000, "currency": "GBP", "tickets": 2 }
}

Fetch the full order with GET /v1/orders/{order_id} when you need more than the ids.

Manage endpoints

Create endpoints in the app under Settings, Developers, Webhooks, or with the API:

curl https://api.konec.to/v1/webhook-endpoints -H "Authorization: Bearer sk_live_..." -H "Idempotency-Key: 7b4e..." \
  -H "Content-Type: application/json" -d '{ "url": "https://example.com/konecto", "events": ["order.paid", "ticket.checked_in"] }'

The response carries the signing secret once. POST /webhook-endpoints/{id}/test sends a sample event and reports the status code your endpoint returned; GET /webhook-endpoints/{id}/deliveries lists attempts; POST .../deliveries/{id}/retry resends one; POST .../rotate-secret issues a new secret.

Verify the signature

Each request has a Konecto-Signature header: t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body"> with your endpoint secret. Reject requests older than five minutes and compare in constant time. The SDK does this for you:

import { constructWebhookEvent } from '@konecto/sdk';
const event = await constructWebhookEvent(rawBody, req.headers.get('Konecto-Signature'), process.env.KONECTO_WEBHOOK_SECRET);

Without the SDK:

import crypto from 'node:crypto';

export function verify(body: string, header: string, secret: string) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto.createHmac('sha256', secret).update(parts.t + '.' + body).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Use the raw request body, not a re-serialised object.

Plain-text version for agents: llm.konec.to/docs/api/webhooks.md