API
Webhooks
En webhook lader Kuvert fortælle dit system, at noget er sket — et kort er udstedt, et beløb er trukket, en ordre er betalt eller refunderet. Hver leverance er signeret, så du kan bevise, at den kom fra os.
Opret et endepunkt
Under Indstillinger → Webhooks tilføjer du den URL, hændelserne skal sendes til. Du får en hemmelighed tilbage — den bruges til at verificere signaturen og skal opbevares som en adgangskode.
Et system kan også tilmelde sig selv med en API-nøgle, uden at nogen åbner dashboardet. Det er sådan en integration, du ikke selv har skrevet, kobler sig på: den får din nøgle og registrerer sin egen adresse.
| Metode | Sti | Gør |
|---|---|---|
| GET | /v1/api/webhooks | Lister dine endepunkter |
| POST | /v1/api/webhooks | Opretter ét og returnerer hemmeligheden |
| PATCH | /v1/api/webhooks | Retter adresse, hændelser eller status |
| DELETE | /v1/api/webhooks/{id} | Fjerner det igen |
curl -X POST https://api.kuvert.dk/v1/api/webhooks \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{"url":"https://dit-system.dk/kuvert","events":["giftcard.redeemed"]}'Hændelser
| Hændelse | Sendes når |
|---|---|
| giftcard.issued | Et gavekort er udstedt. |
| giftcard.redeemed | Et beløb er trukket fra et kort. |
| order.paid | En ordre er betalt. |
| order.refunded | En ordre er refunderet. |
| ticket.issued | En billet er udstedt af en betalt ordre. |
| order.disputed | Køberens kortudsteder har bestridt betalingen. Ordrens gavekort og billetter er sat på pause og kan ikke bruges. |
| order.dispute_closed | Sagen er afgjort. `won: true` betyder, at pausen er hævet og kortene virker igen; `false` at de er annulleret. |
Signaturen
Hver leverance bærer en kuvert-signature-header med et tidsstempel og en HMAC:
kuvert-signature: t=1753440000,v1=6f3a…Signaturen er HMAC-SHA256 i hex over strengen tidsstempel, punktum, den rå krop — beregnet med din endepunkts-hemmelighed:
HMAC_SHA256(secret, "{t}.{raw body}")Verificér i Node
import { createHmac, timingSafeEqual } from 'node:crypto'
function verify(header, rawBody, secret, toleranceSec = 300) {
const parts = Object.fromEntries(
header.split(',').map((kv) => kv.split('=')),
)
const t = Number(parts.t)
if (!t || !parts.v1) return false
// Afvis for gamle leverancer — det stopper genafspilning.
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false
const expected = createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex')
const a = Buffer.from(expected, 'hex')
const b = Buffer.from(parts.v1, 'hex')
return a.length === b.length && timingSafeEqual(a, b)
}Svar hurtigt
- Kvittér med 2xx, så snart du har taget imod. Læg det tunge arbejde i en kø.
- Levering er bedste forsøg — et endepunkt, der er nede, holder aldrig et køb tilbage hos forretningen.
- Regn med at kunne modtage den samme hændelse to gange, og gør din håndtering idempotent.
Krav til URL
Endepunktet skal være en offentligt tilgængelig HTTPS-adresse. Interne adresser afvises — det er en bevidst spærre mod, at et endepunkt bruges til at nå ind i vores eget netværk.