FAQ/Webhook Guide

Webhook Developer Guide

Lær hvordan du sender data fra dine egne systemer til CaapBoard widgets via signerede HTTP requests.

1Overblik

Webhook-systemet gør det muligt at pushe data fra eksterne kilder til dit CaapBoard dashboard. Dette er ideelt til at vise real-time data fra dine egne servere, IoT-enheder, scripts eller tredjepartssystemer.

Sådan virker det

  1. 1. Du opretter en webhook-endpoint i CaapBoard og får en unik ID + secret key
  2. 2. Dit system sender signerede HTTP POST requests til vores ingest-endpoint
  3. 3. CaapBoard validerer signaturen og gemmer dataen
  4. 4. Din widget opdateres med den nye data
🔐
HMAC-SHA256
Sikker signering
100 req/min
Per endpoint
🔄
Idempotent
Sikker retry

2Kom i gang

1Opret en webhook endpoint

Gå til Webhooks-siden og klik på "Ny Webhook". Giv den et beskrivende navn (f.eks. "Raspberry Pi Sensor" eller "Salgsdata").

Vigtigt: Gem din secret key med det samme! Den vises kun én gang ved oprettelse.

2Gem dine credentials

Du modtager tre vigtige værdier:

Webhook URL
https://din-app.dk/webhook/ingest
Webhook ID
550e8400-e29b-41d4-a716-446655440000
Secret Key (64 tegn)
a1b2c3d4e5f6...

3Send din første webhook

Test med curl (se næste sektion for fuld signering):

# Generér signatur og send request
SECRET="din-secret-key"
BODY='{"event":"test","idempotency_key":"test-1","timestamp":"2024-01-15T10:30:00Z","data":{}}'
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)

curl -X POST https://din-app.dk/webhook/ingest \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Id: din-webhook-id" \
  -H "X-Webhook-Signature: sha256=$SIGNATURE" \
  -d "$BODY"

3Autentificering og signering

Alle requests skal signeres med HMAC-SHA256 for at bevise, at de kommer fra dig. Dette forhindrer uautoriserede requests og sikrer dataintegritet.

Signeringsproces

1
Forbered request body
Opret JSON-payload med alle påkrævede felter
2
Beregn HMAC-SHA256
Hash request body med din secret key
3
Tilføj signatur header
Inkluder signaturen i X-Webhook-Signature header
Pseudokode
// 1. Opret payload som JSON string (præcis som den sendes)
payload = '{"event":"sensor.update","idempotency_key":"...",...}'

// 2. Beregn HMAC-SHA256 signatur
signature = HMAC_SHA256(secret_key, payload)

// 3. Konverter til hex string med prefix
header_value = "sha256=" + hex_encode(signature)

// 4. Send request med headers
POST /webhook/ingest
X-Webhook-Id: <din-webhook-id>
X-Webhook-Signature: sha256=<beregnet-signatur>
Content-Type: application/json

<payload>

Sikkerhedsadvarsler

  • • Gem aldrig din secret key i klientside-kode (JavaScript i browseren)
  • • Brug miljøvariabler til at opbevare secret keys
  • • Rotér din secret key hvis du mistænker kompromittering
  • • Send altid requests over HTTPS

4Request format

POST/webhook/ingest

Headers

HeaderPåkrævetBeskrivelse
X-Webhook-IdJaDit webhook endpoint ID (UUID)
X-Webhook-SignatureJaHMAC-SHA256 signatur med sha256= prefix
Content-TypeJaSkal være application/json

Request Body

{
  "event": "sensor.temperature",      // Påkrævet: Event type
  "idempotency_key": "temp-2024-001", // Påkrævet: Unik nøgle
  "timestamp": "2024-01-15T10:30:00Z", // Påkrævet: ISO 8601
  "data": {                            // Valgfri: Din data
    "value": 22.5,
    "unit": "celsius",
    "sensor_id": "living-room"
  }
}

Feltbeskrivelser

event
Type af event. Bruges til at kategorisere og route events. Eksempler:sensor.update,sales.new,status.change
idempotency_key
Unik identifikator for dette event. Hvis samme key sendes igen, returneres success uden at gemme igen (idempotent). Brug f.eks. UUID eller kombination af dato+sensor-id.
timestamp
Hvornår event skete. Skal være valid ISO 8601 format. Eksempel: 2024-01-15T10:30:00Z
data
Valgfrit objekt med din payload-data. Kan indeholde enhver gyldig JSON-struktur.

5Kodeeksempler

🐍Python
import hmac
import hashlib
import json
import requests
from datetime import datetime
import uuid

def send_webhook(url: str, webhook_id: str, secret_key: str,
                 event: str, data: dict) -> dict:
    """
    Send signeret webhook til CaapBoard.

    Args:
        url: Webhook ingest URL
        webhook_id: Dit webhook endpoint ID
        secret_key: Din hemmelige nøgle
        event: Event type (f.eks. "sensor.temperature")
        data: Din payload data
    """
    payload = {
        "event": event,
        "idempotency_key": str(uuid.uuid4()),
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "data": data
    }

    # Konverter til bytes
    body = json.dumps(payload, separators=(',', ':')).encode('utf-8')

    # Beregn signatur
    signature = hmac.new(
        secret_key.encode('utf-8'),
        body,
        hashlib.sha256
    ).hexdigest()

    # Send request
    response = requests.post(
        url,
        data=body,
        headers={
            'Content-Type': 'application/json',
            'X-Webhook-Id': webhook_id,
            'X-Webhook-Signature': f'sha256={signature}'
        }
    )

    return response.json()

# Eksempel: Send temperaturmåling
result = send_webhook(
    url="https://din-app.dk/webhook/ingest",
    webhook_id="din-webhook-id",
    secret_key="din-secret-key",
    event="sensor.temperature",
    data={
        "value": 22.5,
        "unit": "celsius",
        "location": "stue"
    }
)
print(result)  # {"received": true, "id": "evt_xxx"}
🟢Node.js / TypeScript
import crypto from 'crypto';

interface WebhookPayload {
  event: string;
  idempotency_key: string;
  timestamp: string;
  data: Record<string, unknown>;
}

async function sendWebhook(
  url: string,
  webhookId: string,
  secretKey: string,
  event: string,
  data: Record<string, unknown>
): Promise<{ received: boolean; id: string }> {
  const payload: WebhookPayload = {
    event,
    idempotency_key: crypto.randomUUID(),
    timestamp: new Date().toISOString(),
    data
  };

  const body = JSON.stringify(payload);

  // Beregn HMAC-SHA256 signatur
  const signature = crypto
    .createHmac('sha256', secretKey)
    .update(body)
    .digest('hex');

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Webhook-Id': webhookId,
      'X-Webhook-Signature': `sha256=${signature}`
    },
    body
  });

  return response.json();
}

// Eksempel: Send salgsdata
const result = await sendWebhook(
  'https://din-app.dk/webhook/ingest',
  'din-webhook-id',
  process.env.WEBHOOK_SECRET!,
  'sales.new',
  {
    amount: 299.00,
    currency: 'DKK',
    product: 'Premium Abonnement'
  }
);
console.log(result);
💻Bash / cURL
#!/bin/bash

# Konfiguration
WEBHOOK_URL="https://din-app.dk/webhook/ingest"
WEBHOOK_ID="din-webhook-id"
SECRET_KEY="din-secret-key"

# Funktion til at sende webhook
send_webhook() {
    local event="$1"
    local data="$2"

    # Opret payload
    local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
    local idempotency_key=$(uuidgen)

    local body=$(cat <<EOF
{"event":"$event","idempotency_key":"$idempotency_key","timestamp":"$timestamp","data":$data}
EOF
)

    # Beregn signatur
    local signature=$(echo -n "$body" | openssl dgst -sha256 -hmac "$SECRET_KEY" | cut -d' ' -f2)

    # Send request
    curl -s -X POST "$WEBHOOK_URL" \
        -H "Content-Type: application/json" \
        -H "X-Webhook-Id: $WEBHOOK_ID" \
        -H "X-Webhook-Signature: sha256=$signature" \
        -d "$body"
}

# Eksempel: Send systemstatus
send_webhook "system.status" '{"cpu":45,"memory":62,"disk":78}'
🐘PHP
<?php

function sendWebhook(
    string $url,
    string $webhookId,
    string $secretKey,
    string $event,
    array $data
): array {
    $payload = json_encode([
        'event' => $event,
        'idempotency_key' => uniqid('', true),
        'timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
        'data' => $data
    ], JSON_UNESCAPED_SLASHES);

    $signature = hash_hmac('sha256', $payload, $secretKey);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            "X-Webhook-Id: $webhookId",
            "X-Webhook-Signature: sha256=$signature"
        ]
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    return json_decode($response, true);
}

// Eksempel: Send lageroptælling
$result = sendWebhook(
    'https://din-app.dk/webhook/ingest',
    'din-webhook-id',
    $_ENV['WEBHOOK_SECRET'],
    'inventory.update',
    ['sku' => 'PROD-001', 'quantity' => 42]
);
var_dump($result);

6Fejlhåndtering

StatusFejlkodeBeskrivelseHandling
200-Event modtaget succesfuldtIngen handling nødvendig
200duplicate: trueIdempotency key allerede setSikker at ignorere (idempotent)
400invalid_jsonRequest body er ikke gyldig JSONTjek JSON-formatering
400missing_fieldPåkrævet felt manglerTilføj manglende felt (se "field")
400invalid_timestampTimestamp er ikke valid ISO 8601Brug format: YYYY-MM-DDTHH:mm:ssZ
401unknown_endpointWebhook ID ikke fundetTjek X-Webhook-Id header
401invalid_signatureSignatur matcher ikkeTjek secret key og signeringslogik
429rate_limitedFor mange requestsVent "retry_after" sekunder

7Rate limiting

Begrænsninger

  • 100 requests per endpoint per minut
  • Sliding window rate limiting
  • Separate limits per webhook endpoint

Response headers

X-RateLimit-Remaining: 95
Retry-After: 30 (kun ved 429)

Retry-strategi anbefaling

Implementer exponential backoff: vent 1s → 2s → 4s → 8s mellem retry-forsøg. Respektér altid Retry-After header ved 429 responses.

8Best practices

🔑

Sikkerhed

  • • Opbevar secret keys i miljøvariabler
  • • Rotér keys regelmæssigt
  • • Brug altid HTTPS
  • • Log aldrig secret keys
🔄

Idempotency

  • • Brug meningsfulde idempotency keys
  • • F.eks. "sensor-1-2024-01-15-10:30"
  • • Genbrug keys ved retry
  • • Undgå tilfældige keys ved kritiske events

Performance

  • • Batch events når muligt
  • • Implementer connection pooling
  • • Brug async/await for non-blocking calls
  • • Sæt rimelige timeouts
📊

Monitoring

  • • Log webhook responses
  • • Alert ved gentagne fejl
  • • Track latency og success rate
  • • Opret dashboards for webhook health

9Praktiske use cases

🌡️

IoT Sensorer

Raspberry Pi eller Arduino sender temperatur, luftfugtighed og andre målinger til dit dashboard.

{"event":"sensor.reading","data":{"temp":22.5,"humidity":45}}
💰

Forretningsdata

Send realtids salgstal, ordrer eller KPI'er fra dit ERP eller regnskabssystem.

{"event":"sales.daily","data":{"revenue":45000,"orders":127}}
🖥️

Server Monitoring

Cron job der sender CPU, RAM og disk-forbrug hver 5. minut.

{"event":"server.metrics","data":{"cpu":45,"memory":62,"disk":78}}
🔔

Notifikationer

Modtag beskeder fra andre systemer – CI/CD builds, GitHub webhooks, eller custom alerts.

{"event":"build.completed","data":{"status":"success","branch":"main"}}

Klar til at komme i gang?

Opret din første webhook og begynd at sende data til dit dashboard.

Opret Webhook