KuikCodeDocs
Integrations

Scan webhooks

Receive every scan of a code as a signed JSON POST, within a second of it happening.

A scan webhook is an https endpoint of yours that receives one scan.created event per scan. Set it per code under Tracking & Advanced settings, Send scans to a webhook, or for many codes at once through the API. Scan webhooks are included with the Business plan.

Delivery

POST /hooks/kuikcode HTTP/1.1
Content-Type: application/json
User-Agent: KuikCode-Webhook/1.0
X-KuikCode-Event: scan.created
X-KuikCode-Signature: sha256=3f1c...

{
  "event": "scan.created",
  "timestamp": "2026-09-06T14:02:11.482Z",
  "code": { "id": "Ab3dE9xYz", "name": "Spring menu" },
  "scan": {
    "scanId": "01J9...",
    "country": "US",
    "region": "NJ",
    "city": "Montclair",
    "deviceType": "mobile",
    "os": "iOS",
    "browser": "Safari",
    "language": "en",
    "timezone": "America/New_York",
    "destination": "https://example.com/spring-menu",
    "matchedRule": null
  }
}

code.id is the code's nine-character short code. matchedRule names the Smart Rule that decided the scan, or is null. A test fired from the dashboard or the API carries an extra "test": true.

Deliveries are sent once, right after the redirect, with a ten-second timeout. There are no retries, so answer quickly with any 2xx and do your processing afterwards. A code can feed up to five endpoints.

Verify the signature

Each endpoint has a secret, shown after the first save as Copy secret and returned by the API. The signature is sha256= followed by the hex HMAC-SHA256 of the raw request body.

import { createHmac, timingSafeEqual } from "node:crypto"

export function verify(rawBody, header, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex")
  return header?.length === expected.length && timingSafeEqual(Buffer.from(expected), Buffer.from(header))
}

Compute the HMAC over the body bytes exactly as received, before parsing JSON.

Endpoint rules

  • https only, on a public hostname. Private hosts, IP literals, localhost and URLs with credentials are refused when you save.
  • One endpoint per URL per workspace. Pointing another code at the same URL connects it to the existing endpoint and secret.
  • Send test in the editor, or POST /webhooks/{id}/test, fires a sample event and reports the status your server returned.

Managing endpoints through the API

GET /webhooks, POST /webhooks with the codes to connect, PATCH to change the URL or the exact set of connected codes, DELETE to remove. See the reference.

On this page