Spring til indhold

På denne side

Webhooks

Registrér et HTTPS-endpoint, abonnér på hændelser, og Grundfast POST’er en signeret JSON-payload til dig, når de sker. Hver leverance signeres med HMAC-SHA256, så du kan verificere, at den kom fra Grundfast, og leveringen genforsøges med backoff, hvis dit endpoint er nede. Webhooks administreres med din session (i dashboardet eller på /v1/me/webhooks) — ikke med en API-nøgle.

1 · Registrér et endpoint

Opret et endpoint med mindst én event.

POST /v1/me/webhookshttp
POST /v1/me/webhooks          # session-cookie-auth (mint den i dashboardet)
Content-Type: application/json

{
  "url": "https://example.com/hooks/grundfast",   // skal være public https
  "events": ["key.created", "usage.threshold"],
  "description": "prod alerting"                    // valgfri, max 200 tegn
}201
{
  "webhook": {
    "id": "webhook_…",
    "url": "https://example.com/hooks/grundfast",
    "events": ["key.created", "usage.threshold"],
    "enabled": true,
    "description": "prod alerting",
    "secretPrefix": "whsec_a1b2c",
    "secret": "whsec_…",          // ← VISES KUN HER. Gem den; den kan ikke hentes igen.
    "createdAt": "2026-07-03T10:00:00.000Z"
  }
}

Hele administrations-fladen er session-only. En gf_live_/gf_test_-nøgle kan læse data, men ikke administrere webhooks:

GET/v1/me/webhooks

List dine endpoints (uden secrets).

POST/v1/me/webhooks

Registrér et endpoint. Signing-secret returneres ÉN gang.

PATCH/v1/me/webhooks/:id

Opdatér url / events / description / enabled.

DELETE/v1/me/webhooks/:id

Fjern et endpoint.

GET/v1/me/webhooks/:id/deliveries

Seneste leveringsforsøg — audit-loggen.

POST/v1/me/webhooks/:id/test

Send en engangs-signeret ping (event "ping") og se resultatet med det samme.

2 · Event-katalog

Kataloget holdes bevidst lille og stabilt. Hver event bærer et data-objekt med netop disse felter:

key.created{ key_id, name, permission, prefix }

En API-nøgle blev oprettet.

key.revoked{ key_id }

En API-nøgle blev tilbagekaldt.

subscription.updated{ tier }

Abonnementets plan (tier) ændrede sig.

usage.threshold{ tier, threshold_pct, used, allowance, month }

Månedsforbruget krydsede 80% eller 100% af planens kvote (deduplikeret pr. måned+tærskel).

Test-ping’en fra /test bruger event-navnet ping og tælles ikke som en rigtig event.

3 · Payload & headers

Body’en er altid { id, event, created, data } created er ISO-8601 (UTC). Ved siden af signaturen sender vi event-navnet og leverings-id’et som headers, så du kan route og deduplikere uden at parse body’en:

deliveryhttp
POST https://example.com/hooks/grundfast
Content-Type: application/json
User-Agent: Grundfast-Webhooks/1.0
X-Grundfast-Event: key.created
X-Grundfast-Delivery: webhookDelivery_…
X-Grundfast-Signature: sha256=<hex>      # HMAC-SHA256 af den rå body, keyed med din secret

{
  "id": "webhookDelivery_…",            // matcher X-Grundfast-Delivery
  "event": "key.created",
  "created": "2026-07-03T10:00:00.000Z",
  "data": { "key_id": 42, "name": "prod", "permission": "read_write", "prefix": "gf_live_a1b2" }
}

4 · Verificér signaturen

X-Grundfast-Signature er sha256= efterfulgt af den hex-kodede HMAC-SHA256 af den rå request-body, keyed med din whsec_-secret. Genberegn den over de eksakte bytes (før JSON.parse) og sammenlign i konstant tid. Copy-paste:

verify-webhook.tstypescript
import { createHmac, timingSafeEqual } from 'node:crypto';

/**
 * Verificér en Grundfast-webhook-leverance.
 * @param rawBody  De EKSAKTE bytes fra request-body'en (verificér FØR JSON.parse).
 * @param header   Værdien af 'X-Grundfast-Signature' (formen "sha256=<hex>").
 * @param secret   whsec_-secret'en fra da du oprettede endpointet.
 */
export function verifyGrundfastWebhook(
  rawBody: string,
  header: string | null,
  secret: string,
): boolean {
  if (!header) return false;
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex');
  const a = Buffer.from(header);
  const b = Buffer.from(expected);
  // Konstant-tids-sammenligning (undgå timing-læk); længde-tjek først, ellers kaster timingSafeEqual.
  return a.length === b.length && timingSafeEqual(a, b);
}

// Express-eksempel — bemærk: læs den RÅ body, ikke et re-serialiseret objekt.
import express from 'express';
const app = express();

app.post('/hooks/grundfast', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body.toString('utf8');
  const sig = req.header('X-Grundfast-Signature');
  if (!verifyGrundfastWebhook(rawBody, sig, process.env.GRUNDFAST_WEBHOOK_SECRET!)) {
    return res.status(401).send('bad signature');
  }
  const event = JSON.parse(rawBody);
  // …håndtér event.event / event.data. Svar 2xx hurtigt; gør det tunge arbejde async.
  res.sendStatus(200);
});

5 · Retry & levering

En leverance regnes som lykkedes ved en 2xx inden for et 10-sekunders timeout. Alt andet (non-2xx, timeout, netværksfejl) genforsøges med voksende backoff — op til 5 forsøg i alt:

backoff-plantext
forsøg 1(fejl) → vent 1 min
forsøg 2(fejl) → vent 5 min
forsøg 3(fejl) → vent 30 min
forsøg 4(fejl) → vent 2 timer
forsøg 5(fejl) → status "failed" (opgives)

Redirects følges ikke (et 30x fejler leverancen — en ekstra SSRF-værn). Hver leverance står i loggen som pending, success eller failed med antal forsøg og seneste HTTP-status — hent den på GET /v1/me/webhooks/:id/deliveries. Dit endpoint bør være idempotent: svar hurtigt 2xx og lav det tunge arbejde asynkront.

Tilbage til API-dokumentationen eller se den fulde SDK-reference.