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.
List endepunkter
/ v1/ api/ webhooksForretningens endepunkter — også dem, der er oprettet i dashboardet — hver med sin hemmelighed og udfaldet af den seneste leverance.
curl https://api.kuvert.dk/v1/api/webhooks \
-H "Authorization: Bearer $KUVERT_API_KEY"200 OKJSON{
"endpoints": [
{
"id": "weh_5e8a1c3f7b9d42e6a0c4f8b2d6e1a3c9",
"url": "https://dit-system.dk/kuvert",
"events": [
"giftcard.issued",
"giftcard.redeemed"
],
"status": "active",
"secret": "whsec_docs-example-not-a-real-secret",
"lastDeliveryAt": "2026-09-29T09:12:44.000Z",
"lastDeliveryOk": true,
"lastDeliveryDetail": null,
"consecutiveFailures": 0,
"createdAt": "2026-09-01T07:30:00.000Z"
}
]
}Svarfelter
Endepunkterne står under endpoints. Hvert har disse felter:
idstring- Endepunktets id (
weh_…). urlstring- Adressen, hændelserne sendes til.
eventsstring[]- Hændelserne, endepunktet abonnerer på. En tom liste betyder alle.
status"active" | "disabled"- Kun
activefår leverancer. secretstring- Hemmeligheden (
whsec_…), signaturen beregnes med. lastDeliveryAtstring | null- Hvornår der sidst blev forsøgt en leverance.
null, hvis der aldrig er sendt noget. lastDeliveryOkboolean | null- Om den seneste leverance fik 2xx.
nullbetyder aldrig forsøgt — ikke fejlet. lastDeliveryDetailstring | null- Hvorfor den seneste fejlede: en HTTP-status eller en transportfejl.
null, når den lykkedes. consecutiveFailuresinteger- Fejlede leverancer i træk. Den første, der lykkes, nulstiller tallet.
createdAtstring- Hvornår endepunktet blev oprettet.
Statuskoder
200
Listen, eventuelt tom.
En fejl har altid formen { "error": "…" }. Ethvert kald kan desuden svare 401, 403, 429 og 500 — se Fejl.
Bruges ofte sammen med
Tilføj et endepunkt
/ v1/ api/ webhooksRegistrerer en adresse og svarer med endepunktet og dets hemmelighed. Det får leverancer med det samme.
curl -X POST https://api.kuvert.dk/v1/api/webhooks \
-H "Authorization: Bearer $KUVERT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://dit-system.dk/kuvert",
"events": [
"giftcard.issued",
"giftcard.redeemed"
]
}'201 CreatedJSON{
"endpoint": {
"id": "weh_5e8a1c3f7b9d42e6a0c4f8b2d6e1a3c9",
"url": "https://dit-system.dk/kuvert",
"events": [
"giftcard.issued",
"giftcard.redeemed"
],
"status": "active",
"secret": "whsec_docs-example-not-a-real-secret",
"lastDeliveryAt": null,
"lastDeliveryOk": null,
"lastDeliveryDetail": null,
"consecutiveFailures": 0,
"createdAt": "2026-09-29T09:00:00.000Z"
}
}Krop
urlstringPåkrævet- En offentlig https-adresse, højst 2048 tegn. Interne og lokale adresser afvises.
eventsstring[]Påkrævet- Hændelserne, der skal sendes. En tom liste betyder alle — også dem, der kommer til senere.
Svarfelter
Endepunktet står under endpoint, med de samme felter som ved List endepunkter.
Statuskoder
201
Endepunktet er oprettet.
400
invalid_webhook_endpointKroppen matcher ikke: adressen er ikke en offentlig https-adresse, eller en hændelse findes ikke.
En fejl har altid formen { "error": "…" }. Ethvert kald kan desuden svare 401, 403, 429 og 500 — se Fejl.
Bruges ofte sammen med
Ret et endepunkt
/ v1/ api/ webhooksRetter adresse, hændelser eller status. Endepunktets id står i kroppen, ikke i stien — send kun de felter, du vil ændre.
curl -X PATCH https://api.kuvert.dk/v1/api/webhooks \
-H "Authorization: Bearer $KUVERT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "weh_5e8a1c3f7b9d42e6a0c4f8b2d6e1a3c9",
"status": "disabled"
}'200 OKJSON{
"endpoint": {
"id": "weh_5e8a1c3f7b9d42e6a0c4f8b2d6e1a3c9",
"url": "https://dit-system.dk/kuvert",
"events": [
"giftcard.issued",
"giftcard.redeemed"
],
"status": "disabled",
"secret": "whsec_docs-example-not-a-real-secret",
"lastDeliveryAt": "2026-09-29T09:12:44.000Z",
"lastDeliveryOk": true,
"lastDeliveryDetail": null,
"consecutiveFailures": 0,
"createdAt": "2026-09-01T07:30:00.000Z"
}
}Krop
idstringPåkrævet- Endepunktets id (
weh_…). urlstringValgfri- Ny adresse, med samme krav som ved oprettelse.
eventsstring[]Valgfri- En ny liste af hændelser. Den erstatter den gamle helt.
status"active" | "disabled"Valgfridisabledstandser leverancerne uden at slette endepunktet.
Svarfelter
Det rettede endepunkt står under endpoint.
Statuskoder
200
Endepunktet er rettet.
400
invalid_requestKroppen matcher ikke — typisk et manglende
id.404
webhook_endpoint_not_foundEndepunktet findes ikke på din forretning.
En fejl har altid formen { "error": "…" }. Ethvert kald kan desuden svare 401, 403, 429 og 500 — se Fejl.
Bruges ofte sammen med
Slet et endepunkt
/ v1/ api/ webhooks/ {id}Fjerner endepunktet for altid. Vil du bare holde pause, så sæt status til disabled i stedet.
curl -X DELETE https://api.kuvert.dk/v1/api/webhooks/weh_5e8a1c3f7b9d42e6a0c4f8b2d6e1a3c9 \
-H "Authorization: Bearer $KUVERT_API_KEY"200 OKJSON{
"deleted": true
}Stiparametre
idstringPåkrævet- Endepunktets id (
weh_…).
Svarfelter
deletedboolean- Altid
true.
Statuskoder
200
Endepunktet er slettet.
404
webhook_endpoint_not_foundEndepunktet findes ikke på din forretning.
En fejl har altid formen { "error": "…" }. Ethvert kald kan desuden svare 401, 403, 429 og 500 — se Fejl.
Bruges ofte sammen med
Hver hændelse sendes som en POST med en JSON-krop i fire felter: id er leverancens eget id (whevt_…), type er hændelsen, createdAt er tidspunktet, og data er det, hændelsen handler om. Tre headers følger med: content-type, kuvert-event-type og kuvert-signature.
POST /kuvert HTTP/1.1
Host: dit-system.dk
content-type: application/json
kuvert-event-type: giftcard.redeemed
kuvert-signature: t=1790673164,v1=067aae7b381a2e42b518ef5781e9747638aa4c987e4f68396bef9a06a78d71a8
{"id":"whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7","type":"giftcard.redeemed","createdAt":"2026-09-29T09:12:44.118Z","data":{"code":"KUV-7K9F-2XQD","amountOre":6400,"remainingBalanceOre":43600}}Signaturen i eksemplet er ægte. Med hemmeligheden whsec_docs-example-not-a-real-secret giver opskriften under Signaturen præcis den v1 — kør din egen verificering på den, med tidstolerancen slået fra, da tidspunktet ligger i fortiden.
giftcard.issued
Et gavekort er udstedt.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "giftcard.issued",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"code": "KUV-M4TQ-8WZR",
"initialOre": 50000,
"orderId": "ord_7d3f0b9c1e2a45f68b4c9d0e1f2a3b5c"
}
}giftcard.redeemed
Et beløb er trukket fra et kort.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "giftcard.redeemed",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"code": "KUV-7K9F-2XQD",
"amountOre": 6400,
"remainingBalanceOre": 43600
}
}order.paid
En ordre er betalt.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "order.paid",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"orderId": "ord_7d3f0b9c1e2a45f68b4c9d0e1f2a3b5c",
"totalOre": 50000,
"cardCodes": [
"KUV-M4TQ-8WZR"
]
}
}order.refunded
En ordre er refunderet.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "order.refunded",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"orderId": "ord_7d3f0b9c1e2a45f68b4c9d0e1f2a3b5c",
"refundedOre": 50500
}
}ticket.issued
En billet er udstedt af en betalt ordre.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "ticket.issued",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"code": "TKT-H3NW-7PKC",
"eventId": "evt_1a9c4e7b2d5f48a3b6c9e0d2f4a7b1c8",
"orderId": "ord_7d3f0b9c1e2a45f68b4c9d0e1f2a3b5c"
}
}order.disputed
Køberens kortudsteder har bestridt betalingen. Ordrens gavekort og billetter er sat på pause og kan ikke bruges.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "order.disputed",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"orderId": "ord_7d3f0b9c1e2a45f68b4c9d0e1f2a3b5c",
"frozenCards": 1,
"frozenTickets": 0
}
}order.dispute_closed
Sagen er afgjort. won: true betyder, at pausen er hævet og kortene virker igen; false at de er annulleret.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "order.dispute_closed",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"orderId": "ord_7d3f0b9c1e2a45f68b4c9d0e1f2a3b5c",
"won": true,
"disputedOre": 50500
}
}booking.created
Et bord er booket — af gæsten på bookingsiden, i telefonen eller ved døren.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "booking.created",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"bookingId": "bkg_6c0e3a9f5b1d47e2a8c4f0b6d2e9a5c1",
"date": "2026-10-02",
"startsAt": "2026-10-02T17:30:00.000Z",
"endsAt": "2026-10-02T19:30:00.000Z",
"guests": 4,
"status": "confirmed",
"source": "storefront",
"guestName": "Mette Hansen",
"guestEmail": "mette@dit-domaene.dk",
"guestPhone": "+45 12 34 56 78",
"tableIds": [
"btb_0d4a8e2c6f1b43a9b7e5c3d1f9a2e6b8"
]
}
}booking.changed
En bordbooking har fået ny tid, dato, antal eller borde, eller en ny status. Hele bookingen følger med, og status siger, hvor den står.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "booking.changed",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"bookingId": "bkg_6c0e3a9f5b1d47e2a8c4f0b6d2e9a5c1",
"date": "2026-10-02",
"startsAt": "2026-10-02T17:30:00.000Z",
"endsAt": "2026-10-02T19:30:00.000Z",
"guests": 6,
"status": "confirmed",
"source": "storefront",
"guestName": "Mette Hansen",
"guestEmail": "mette@dit-domaene.dk",
"guestPhone": "+45 12 34 56 78",
"tableIds": [
"btb_0d4a8e2c6f1b43a9b7e5c3d1f9a2e6b8"
]
}
}booking.cancelled
En bordbooking er aflyst, af gæsten eller af restauranten. Bookinger, der flyttes ind fra et andet system, sender ingen hændelser.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "booking.cancelled",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"bookingId": "bkg_6c0e3a9f5b1d47e2a8c4f0b6d2e9a5c1",
"date": "2026-10-02",
"startsAt": "2026-10-02T17:30:00.000Z",
"endsAt": "2026-10-02T19:30:00.000Z",
"guests": 4,
"status": "cancelled",
"source": "storefront",
"guestName": "Mette Hansen",
"guestEmail": "mette@dit-domaene.dk",
"guestPhone": "+45 12 34 56 78",
"tableIds": [
"btb_0d4a8e2c6f1b43a9b7e5c3d1f9a2e6b8"
]
}
}event.published
Et arrangement er sat til salg. Hele aftenen følger med — navn, tider, sted, serie, line-up og genrer — i samme form som listen på GET /v1/merchants/{slug}, med eventId i stedet for id.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "event.published",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"eventId": "evt_1a9c4e7b2d5f48a3b6c9e0d2f4a7b1c8",
"slug": "vortex-dimension-2026-10-03",
"name": "Vortex Dimension",
"startsAt": "2026-10-03T21:00:00.000Z",
"endsAt": "2026-10-04T03:00:00.000Z",
"venue": "Club Vortex",
"bannerUrl": "https://api.kuvert.dk/v1/media/med_2b7e4c9a1f3d48e6b0a5c8d2f1e7a4b9",
"bannerWidth": 1080,
"bannerHeight": 1350,
"salesCloseAt": "2026-10-03T21:00:00.000Z",
"status": "published",
"cancelNote": null,
"series": null,
"seriesDate": null,
"subtitle": "No Boyz On Deck",
"lineup": [
{
"name": "Faustix",
"role": "DJ",
"time": "2026-10-03T23:00:00.000Z"
}
],
"genres": [
"House",
"Techno"
],
"doorsAt": "2026-10-03T21:00:00.000Z",
"lastEntryAt": "2026-10-04T01:30:00.000Z",
"dressCode": null,
"ticketsUrl": null
}
}event.updated
Noget offentligt på et udgivet arrangement er ændret. changedFields siger hvad — fx lineup, eller tiers, når en pris er ændret.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "event.updated",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"eventId": "evt_1a9c4e7b2d5f48a3b6c9e0d2f4a7b1c8",
"slug": "vortex-dimension-2026-10-03",
"name": "Vortex Dimension",
"startsAt": "2026-10-03T21:00:00.000Z",
"endsAt": "2026-10-04T03:00:00.000Z",
"venue": "Club Vortex",
"bannerUrl": "https://api.kuvert.dk/v1/media/med_2b7e4c9a1f3d48e6b0a5c8d2f1e7a4b9",
"bannerWidth": 1080,
"bannerHeight": 1350,
"salesCloseAt": "2026-10-03T21:00:00.000Z",
"status": "published",
"cancelNote": null,
"series": null,
"seriesDate": null,
"subtitle": "No Boyz On Deck",
"lineup": [
{
"name": "Faustix",
"role": "DJ",
"time": "2026-10-03T23:00:00.000Z"
}
],
"genres": [
"House",
"Techno"
],
"doorsAt": "2026-10-03T21:00:00.000Z",
"lastEntryAt": "2026-10-04T01:30:00.000Z",
"dressCode": null,
"ticketsUrl": null,
"changedFields": [
"lineup"
]
}
}event.canceled
Et udgivet arrangement er aflyst. cancelNote er den besked, I skrev til gæsterne.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "event.canceled",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"eventId": "evt_1a9c4e7b2d5f48a3b6c9e0d2f4a7b1c8",
"slug": "vortex-dimension-2026-10-03",
"name": "Vortex Dimension",
"startsAt": "2026-10-03T21:00:00.000Z",
"endsAt": "2026-10-04T03:00:00.000Z",
"venue": "Club Vortex",
"bannerUrl": "https://api.kuvert.dk/v1/media/med_2b7e4c9a1f3d48e6b0a5c8d2f1e7a4b9",
"bannerWidth": 1080,
"bannerHeight": 1350,
"salesCloseAt": "2026-10-03T21:00:00.000Z",
"status": "canceled",
"cancelNote": "Aftenen er aflyst. Alle billetter refunderes automatisk.",
"series": null,
"seriesDate": null,
"subtitle": "No Boyz On Deck",
"lineup": [
{
"name": "Faustix",
"role": "DJ",
"time": "2026-10-03T23:00:00.000Z"
}
],
"genres": [
"House",
"Techno"
],
"doorsAt": "2026-10-03T21:00:00.000Z",
"lastEntryAt": "2026-10-04T01:30:00.000Z",
"dressCode": null,
"ticketsUrl": null
}
}event.unpublished
Et udgivet arrangement er sat tilbage til kladde. En kladde sender aldrig hændelser.
{
"id": "whevt_c81f5a2d9e3b47c6a0d4e8f1b5c9a2d7",
"type": "event.unpublished",
"createdAt": "2026-09-29T09:12:44.118Z",
"data": {
"eventId": "evt_1a9c4e7b2d5f48a3b6c9e0d2f4a7b1c8",
"slug": "vortex-dimension-2026-10-03",
"name": "Vortex Dimension",
"startsAt": "2026-10-03T21:00:00.000Z",
"endsAt": "2026-10-04T03:00:00.000Z",
"venue": "Club Vortex",
"bannerUrl": "https://api.kuvert.dk/v1/media/med_2b7e4c9a1f3d48e6b0a5c8d2f1e7a4b9",
"bannerWidth": 1080,
"bannerHeight": 1350,
"salesCloseAt": "2026-10-03T21:00:00.000Z",
"status": "draft",
"cancelNote": null,
"series": null,
"seriesDate": null,
"subtitle": "No Boyz On Deck",
"lineup": [
{
"name": "Faustix",
"role": "DJ",
"time": "2026-10-03T23:00:00.000Z"
}
],
"genres": [
"House",
"Techno"
],
"doorsAt": "2026-10-03T21:00:00.000Z",
"lastEntryAt": "2026-10-04T01:30:00.000Z",
"dressCode": null,
"ticketsUrl": null
}
}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}")# t og v1 fra kuvert-signature, body.json er den rå krop, præcis som den kom ind
printf '%s.%s' "$t" "$(cat body.json)" \
| openssl dgst -sha256 -hmac "$KUVERT_WEBHOOK_SECRET" -hex
# Sammenlign resultatet med v1- 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.
- Vi venter højst 5 sekunder på svaret. Alt andet end 2xx inden da tæller som en fejlet leverance — også en omdirigering, som vi ikke følger.
Der er ingen genforsøg. Hver hændelse sendes én gang til hvert endepunkt, og en leverance, der fejler, sendes ikke igen.
Hvordan den seneste leverance gik, står på endepunktet: List endepunkter svarer med lastDeliveryAt, lastDeliveryOk, lastDeliveryDetail og consecutiveFailures, og det samme står under Indstillinger → Webhooks. Vi slår aldrig et endepunkt fra af os selv — kun du kan sætte det til disabled.
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.
Var siden nyttig?