> ## Documentation Index
> Fetch the complete documentation index at: https://storekit.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Live-orderbord voor de pass

> Een fullscreen orderbord dat elk open ticket toont en het door New, Preparing, Ready en Done laat bewegen zodra er storekit-order-events binnenkomen.

Een muurscherm voor de pass, de bar of de front counter dat open bestellingen als kaarten toont en ze doorschuift naarmate hun status in storekit verandert. Grote ordercodes, itemlijsten, een timer die rood wordt wanneer een bestelling te lang heeft gewacht en je eigen keuze van wat te tonen. Niemand raakt het aan: het volgt het dashboard.

> [!TIP]
>
> storekit heeft een ingebouwde [Kitchen Display](/docs/nl/guides/orders/kitchen-display) waarop de keuken tickets kan bumpen. Bouw deze recipe wanneer je een read-only scherm wilt met een andere lay-out — een klantgericht "klaar"-bord, een counter-weergave die alleen afhaalorders toont, of een barscherm dat alleen drankjes toont.

## Hoe het werkt

1. storekit stuurt lifecycle-events voor bestellingen naar je app: `order.created.v2` bevat de volledige bestelling; `order.preparing`, `order.ready_for_pickup`, `order.out_for_delivery`, `order.completed` en `order.canceled` bevatten alleen de id, code en nieuwe status.
2. Je app houdt één kaart per bestelling bij en werkt de kolom ervan bij op basis van elk event.
3. Een browserpagina ververst zichzelf om de paar seconden en toont de kaarten in kolommen.

## Wat je nodig hebt

* Webhooks ingeschakeld op je storekit-abonnement (**Settings** → **Developers**).
* Een [Lovable](https://lovable.dev)- of [v0](https://v0.dev)-account. Beide hebben een backend nodig voor het webhook-endpoint en een kleine database — de prompt vraagt de builder er een te deployen en je de URL te geven. [Replit](https://replit.com) werkt ook.
* Een scherm met een browser (een tv met een goedkope stick, een oude iPad of laptop).

## Stap 1: Bouw de app

Plak het hele blok in de builder in één bericht. Pas eerst **MY BOARD** aan als je een klantgerichte of alleen-drankjes-variant wilt.

[![](https://mintcdn.com/storekit/IGeB_84apyho3Z6U/images/logos/lovable.svg?fit=max\&auto=format\&n=IGeB_84apyho3Z6U\&q=85\&s=cc21d11769d5e6b686fed56390a88f5e) Build with Lovable](https://lovable.dev/#prompt=I%20want%20a%20live%20order%20board%20for%20my%20restaurant%3A%20a%20full-screen%20web%20page%20that%20shows%20open%20orders%20as%20cards%20in%20columns%20and%20moves%20them%20along%20as%20storekit%20tells%20it%20about%20status%20changes.%20Nobody%20interacts%20with%20the%20board%3B%20it%20just%20stays%20accurate.%20storekit%20sends%20the%20status%20changes%20as%20webhooks.%0A%0ABuild%20it%20with%20a%20backend%20that%20has%20a%20public%20HTTPS%20URL%20for%20the%20webhook%20\(deploy%20it%20and%20tell%20me%20the%20URL\)%2C%20a%20small%20database%20for%20the%20current%20orders%2C%20and%20a%20front%20end%20designed%20for%20a%20TV.%0A%0ACONTEXT%20ABOUT%20STOREKIT%0A%0AI%20run%20a%20hospitality%20business%20on%20storekit%20\(storekit.com\)%2C%20an%20online%20ordering%20and%20payments%20platform%20for%20restaurants%2C%20caf%C3%A9s%20and%20bars.%20storekit%20can%20notify%20my%20own%20app%20about%20things%20that%20happen%20in%20my%20store%20by%20sending%20webhooks.%0A%0AHow%20storekit%20webhooks%20work%3A%0A-%20Each%20event%20is%20an%20HTTP%20POST%20with%20a%20JSON%20body%20to%20a%20URL%20I%20register%20in%20the%20storekit%20dashboard.%0A-%20The%20body%20has%20the%20shape%20%7B%20%22event%22%3A%20%22%3Cevent%20type%3E%22%2C%20%22data%22%3A%20%7B%20...%20%7D%20%7D.%20Two%20exceptions%3A%20the%20legacy%20order.created%20event%20\(do%20not%20use%20it%3B%20use%20order.created.v2\)%20sends%20the%20bare%20order%20object%2C%20and%20the%20order.refund.created%20%2F%20order.refund.succeeded%20%2F%20order.refund.failed%20events%20put%20order%2C%20customer%2C%20venue%20and%20refund%20next%20to%20event%20instead%20of%20inside%20data.%0A-%20Requests%20are%20signed%20with%20Svix.%20Every%20request%20carries%20the%20headers%20svix-id%2C%20svix-timestamp%20and%20svix-signature.%20Verify%20them%20with%20the%20official%20%60svix%60%20library%20for%20your%20language%2C%20using%20a%20secret%20I%20will%20provide%20in%20an%20environment%20variable%20called%20STOREKIT_WEBHOOK_SECRET.%20Reject%20anything%20that%20fails%20verification%20with%20HTTP%20400.%0A-%20My%20endpoint%20must%20respond%20with%20a%202xx%20status%20within%2015%20seconds.%20Save%20the%20event%20to%20the%20database%20first%2C%20respond%20200%2C%20then%20do%20the%20work%20from%20the%20saved%20copy.%0A-%20Once%20I%20have%20responded%20200%2C%20storekit%20will%20not%20send%20that%20event%20again%2C%20so%20the%20work%20must%20not%20be%20lost%3A%20store%20each%20event%20with%20a%20status%20\(pending%2C%20done%2C%20failed\)%20and%20only%20mark%20it%20done%20after%20the%20work%20has%20actually%20succeeded.%20On%20startup%20and%20on%20a%20timer%2C%20pick%20up%20anything%20still%20pending%20or%20failed%20and%20retry%20it.%20Never%20do%20the%20work%20only%20in%20memory%20after%20responding.%0A-%20The%20same%20event%20can%20occasionally%20be%20delivered%20more%20than%20once.%20Use%20the%20svix-id%20header%20as%20the%20unique%20key%20for%20the%20saved%20event%3A%20a%20repeat%20of%20an%20event%20already%20saved%20is%20ignored%2C%20whatever%20its%20status.%0A-%20Any%20page%20or%20endpoint%20protected%20by%20a%20password%20must%20be%20served%20over%20HTTPS%20only%3B%20redirect%20or%20refuse%20plain%20HTTP.%0A-%20Money%20fields%20\(total%2C%20tip%2C%20deliveryFee%2C%20discountTotal%2C%20item%20price%2C%20modifier%20price\)%20are%20integers%20in%20minor%20units%3A%202500%20means%20%C2%A325.00.%0A-%20orderType%20is%20one%20of%20Delivery%2C%20Pickup%2C%20InStore%2C%20Curbside%2C%20CateringDelivery%2C%20CateringPickup%20or%20Billpay.%0A-%20table%20is%20only%20present%20for%20in-store%20orders%3B%20deliveryAddress%20and%20pickUpTime%20only%20for%20delivery%20orders.%20When%20they%20do%20not%20apply%20the%20key%20is%20omitted%2C%20so%20treat%20a%20missing%20key%20the%20same%20as%20null.%20notes%20and%20items%5B%5D.note%20can%20be%20null%20or%20empty.%20customer.companyName%20and%20customer.vatNumber%20appear%20only%20when%20supplied.%0A%0AExample%20order.created.v2%20payload%3A%0A%0A%7B%0A%20%20%22event%22%3A%20%22order.created.v2%22%2C%0A%20%20%22data%22%3A%20%7B%0A%20%20%20%20%22id%22%3A%20%22ord_abc123%22%2C%0A%20%20%20%20%22code%22%3A%20%22A1B2%22%2C%0A%20%20%20%20%22asap%22%3A%20true%2C%0A%20%20%20%20%22total%22%3A%202500%2C%0A%20%20%20%20%22tip%22%3A%20250%2C%0A%20%20%20%20%22deliveryFee%22%3A%20299%2C%0A%20%20%20%20%22discountTotal%22%3A%200%2C%0A%20%20%20%20%22orderType%22%3A%20%22Delivery%22%2C%0A%20%20%20%20%22pickUpTime%22%3A%20%222024-01-15T10%3A45%3A00Z%22%2C%0A%20%20%20%20%22channel%22%3A%20%22WEB%22%2C%0A%20%20%20%20%22poNumber%22%3A%20null%2C%0A%20%20%20%20%22createdAt%22%3A%20%222024-01-15T10%3A30%3A00Z%22%2C%0A%20%20%20%20%22deliveryTime%22%3A%20%222024-01-15T11%3A00%3A00Z%22%2C%0A%20%20%20%20%22notes%22%3A%20%22Ring%20doorbell%22%2C%0A%20%20%20%20%22customer%22%3A%20%7B%0A%20%20%20%20%20%20%22firstName%22%3A%20%22John%22%2C%0A%20%20%20%20%20%20%22lastName%22%3A%20%22Doe%22%2C%0A%20%20%20%20%20%20%22email%22%3A%20%22john%40example.com%22%2C%0A%20%20%20%20%20%20%22phone%22%3A%20%22%2B44123456789%22%2C%0A%20%20%20%20%20%20%22marketingConsent%22%3A%20true%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22items%22%3A%20%5B%0A%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22name%22%3A%20%22Margherita%20Pizza%22%2C%0A%20%20%20%20%20%20%20%20%22printName%22%3A%20%22Margherita%22%2C%0A%20%20%20%20%20%20%20%20%22price%22%3A%201200%2C%0A%20%20%20%20%20%20%20%20%22quantity%22%3A%201%2C%0A%20%20%20%20%20%20%20%20%22plu%22%3A%20null%2C%0A%20%20%20%20%20%20%20%20%22posId%22%3A%20null%2C%0A%20%20%20%20%20%20%20%20%22taxRate%22%3A%2020%2C%0A%20%20%20%20%20%20%20%20%22note%22%3A%20null%2C%0A%20%20%20%20%20%20%20%20%22modifiers%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%7B%20%22name%22%3A%20%22Extra%20cheese%22%2C%20%22printName%22%3A%20%22Cheese%22%2C%20%22price%22%3A%20150%2C%20%22quantity%22%3A%201%2C%20%22plu%22%3A%20null%2C%20%22posId%22%3A%20null%2C%20%22taxRate%22%3A%2020%20%7D%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%5D%2C%0A%20%20%20%20%22venue%22%3A%20%7B%0A%20%20%20%20%20%20%22id%22%3A%201234%2C%0A%20%20%20%20%20%20%22name%22%3A%20%22My%20Restaurant%22%2C%0A%20%20%20%20%20%20%22slug%22%3A%20%22my-restaurant%22%2C%0A%20%20%20%20%20%20%22address%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22street1%22%3A%20%22123%20Main%20St%22%2C%0A%20%20%20%20%20%20%20%20%22street2%22%3A%20%22%22%2C%0A%20%20%20%20%20%20%20%20%22city%22%3A%20%22London%22%2C%0A%20%20%20%20%20%20%20%20%22postCode%22%3A%20%22W1A%201AA%22%2C%0A%20%20%20%20%20%20%20%20%22country%22%3A%20%22UK%22%2C%0A%20%20%20%20%20%20%20%20%22companyName%22%3A%20%22My%20Restaurant%20Ltd%22%2C%0A%20%20%20%20%20%20%20%20%22coordinates%22%3A%20%7B%20%22latitude%22%3A%2051.5074%2C%20%22longitude%22%3A%20-0.1278%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22deliveryAddress%22%3A%20%7B%0A%20%20%20%20%20%20%22street1%22%3A%20%22456%20Oak%20Ave%22%2C%0A%20%20%20%20%20%20%22street2%22%3A%20%22Flat%202%22%2C%0A%20%20%20%20%20%20%22city%22%3A%20%22London%22%2C%0A%20%20%20%20%20%20%22postCode%22%3A%20%22E1%206AN%22%2C%0A%20%20%20%20%20%20%22country%22%3A%20%22UK%22%2C%0A%20%20%20%20%20%20%22notes%22%3A%20null%2C%0A%20%20%20%20%20%20%22coordinates%22%3A%20%7B%20%22latitude%22%3A%2051.5155%2C%20%22longitude%22%3A%20-0.0722%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%0A%0AThe%20full%20list%20of%20event%20types%20and%20payloads%20is%20at%20https%3A%2F%2Fstorekit.com%2Fdocs%2Fdevelopers%2Fwebhooks%2Fwebhook-events%0A%0ATHE%20STATUS%20EVENTS%0A%0ABesides%20order.created.v2%2C%20storekit%20sends%20these%20events%20for%20the%20same%20order%20as%20it%20moves%20through%20the%20kitchen.%20order.preparing%2C%20order.ready_for_pickup%2C%20order.out_for_delivery%2C%20order.completed%20and%20order.canceled%20all%20have%20this%20smaller%20payload%2C%20with%20data.order.status%20set%20to%20the%20new%20status%3A%0A%0A%7B%0A%20%20%22event%22%3A%20%22order.preparing%22%2C%0A%20%20%22data%22%3A%20%7B%0A%20%20%20%20%22order%22%3A%20%7B%20%22id%22%3A%20%22ord_abc123%22%2C%20%22code%22%3A%20%22A1B2%22%2C%20%22status%22%3A%20%22preparing%22%20%7D%2C%0A%20%20%20%20%22venue%22%3A%20%7B%20%22id%22%3A%201234%2C%20%22name%22%3A%20%22My%20Restaurant%22%2C%20%22slug%22%3A%20%22my-restaurant%22%20%7D%0A%20%20%7D%0A%7D%0A%0ASo%20the%20order%20id%20is%20data.id%20for%20the%20full%20payload%20and%20data.order.id%20for%20the%20smaller%20one.%20Write%20one%20function%20that%20returns%20the%20id%20and%20code%20from%20either%20shape.%20Treat%20canceled%20as%20%22remove%20from%20board%22%20and%20out_for_delivery%20as%20%22Done%22.%20There%20is%20no%20accepted%20or%20rejected%20event%3A%20an%20order%20stays%20in%20New%20until%20its%20first%20status%20event%20arrives.%0A%0AEvents%20can%20arrive%20out%20of%20order%20or%20twice.%20Store%20a%20rank%20per%20status%20\(created%200%2C%20preparing%202%2C%20ready_for_pickup%203%2C%20out_for_delivery%204%2C%20completed%204%2C%20canceled%209\)%20and%20never%20move%20a%20card%20to%20a%20lower%20rank%20than%20it%20already%20has.%0A%0AWHAT%20TO%20BUILD%0A%0A1.%20POST%20%2Fwebhooks%2Fstorekit%20%E2%80%94%20verify%20the%20Svix%20signature%20with%20the%20official%20svix%20library%20and%20STOREKIT_WEBHOOK_SECRET%20against%20the%20raw%20request%20body%3B%20400%20on%20failure.%20Save%20the%20event%20and%20return%20200%20immediately%3B%20update%20the%20board%20state%20afterwards.%20Ignore%20any%20svix-id%20already%20saved.%0A2.%20An%20orders%20table%20keyed%20by%20storekit%20order%20id%20with%3A%20code%2C%20status%2C%20rank%2C%20orderType%2C%20table%20name%20\(or%20null\)%2C%20customer%20first%20name%2C%20items%20\(name%2C%20quantity%2C%20modifier%20names%2C%20notes\)%2C%20order%20notes%2C%20createdAt%2C%20deliveryTime%2C%20venue%20name%2C%20last%20updated.%20The%20full%20order.created.v2%20payload%20fills%20or%20refreshes%20every%20field%3B%20small%20payloads%20update%20only%20status%2C%20rank%20and%20last%20updated.%20If%20a%20small%20payload%20arrives%20for%20an%20order%20the%20board%20has%20never%20seen%2C%20create%20a%20card%20with%20just%20the%20code%20and%20status%20so%20it%20is%20not%20lost.%0A3.%20GET%20%2Fboard%2F%3CBOARD_TOKEN%3E%20%E2%80%94%20the%20full-screen%20page%2C%20where%20BOARD_TOKEN%20is%20a%20long%20random%20secret%20from%20an%20environment%20variable.%20There%20is%20no%20login%20because%20it%20runs%20on%20a%20screen%20behind%20the%20counter%2C%20so%20the%20token%20in%20the%20URL%20is%20the%20only%20thing%20keeping%20strangers%20out%3A%20any%20request%20to%20%2Fboard%20or%20%2Fapi%2Fboard%20without%20the%20exact%20token%20gets%20404%20and%20nothing%20else%2C%20and%20the%20token%20must%20never%20appear%20in%20logs%20or%20error%20pages.%20Below%20I%20write%20%2Fboard%20for%20short%3B%20always%20include%20the%20token%20segment.%20The%20page%20must%20not%20include%20customer%20surname%2C%20phone%2C%20email%20or%20delivery%20address%20anywhere%20in%20the%20page%20or%20its%20data%20requests.%20Columns%2C%20left%20to%20right%3A%20New%20\(created\)%2C%20Preparing%2C%20Ready%20\(ready_for_pickup\)%2C%20Done%20\(completed%2C%20out_for_delivery\).%20Cards%20show%3A%20the%20order%20code%20very%20large%2C%20orderType%20and%20table%20name%2C%20the%20customer's%20first%20name%2C%20the%20item%20list%20as%20%222%20%C3%97%20Margherita%20Pizza%22%20with%20modifiers%20indented%20and%20notes%20in%20italics%2C%20and%20a%20timer%20showing%20minutes%20since%20createdAt.%20Cards%20are%20ordered%20oldest%20first%20inside%20each%20column.%20Cards%20in%20Done%20disappear%20DONE_MINUTES%20after%20they%20entered%20Done%20\(default%2010\).%20Cards%20for%20canceled%20orders%20disappear%20immediately.%0A4.%20The%20page%20refreshes%20its%20data%20every%205%20seconds%20without%20flicker%20\(fetch%20JSON%20from%20GET%20%2Fapi%2Fboard%2F%3CBOARD_TOKEN%3E%20and%20re-render%2C%20or%20use%20server-sent%20events\).%20If%20a%20refresh%20fails%2C%20keep%20showing%20the%20last%20data%20and%20show%20a%20small%20%22reconnecting%22%20label%3B%20never%20show%20a%20blank%20board.%0A5.%20Timer%20colours%3A%20default%20until%20WARN_MINUTES%20\(default%2010\)%2C%20amber%20until%20LATE_MINUTES%20\(default%2020\)%2C%20red%20after.%20For%20orders%20with%20a%20deliveryTime%20in%20the%20future%2C%20count%20down%20to%20deliveryTime%20instead%20and%20label%20it%20%22for%2011%3A00%22.%0A6.%20Design%20for%20a%201080p%20TV%20viewed%20from%20three%20metres%3A%20dark%20background%2C%20minimum%2028px%20body%20text%2C%20order%20codes%20at%20least%2096px%2C%20no%20hover-only%20information%2C%20no%20scrollbars%20%E2%80%94%20if%20a%20column%20overflows%2C%20shrink%20cards%20gracefully%20and%20show%20%22%2BN%20more%22%20at%20the%20bottom.%0A7.%20Filters%20via%20query%20string%20so%20I%20can%20point%20different%20screens%20at%20different%20views%3A%20%3Ftypes%3DPickup%2CDelivery%20shows%20only%20those%20orderTypes%3B%20%3Fcolumns%3DReady%20shows%20only%20that%20column%20with%20much%20bigger%20cards%20for%20a%20customer-facing%20%22ready%20to%20collect%22%20screen%3B%20%3Fvenue%3D%3Cslug%3E%20if%20the%20account%20has%20more%20than%20one%20venue.%0A8.%20GET%20%2Fhealth%20that%20returns%20200%20and%20%7B%20%22ok%22%3A%20true%20%7D%20and%20nothing%20else.%20Every%20other%20route%20must%20be%20either%20%2Fboard%2F%3CBOARD_TOKEN%3E%2C%20%2Fapi%2Fboard%2F%3CBOARD_TOKEN%3E%20or%20the%20webhook.%0A%0AMY%20BOARD%0A%0A-%20Timezone%3A%20TIMEZONE%20environment%20variable%3B%20show%20all%20times%20in%20it.%0A-%20Show%20the%20item%20list%20on%20cards%3A%20yes.%0A-%20Customer-facing%20variant%20I%20will%20run%20on%20a%20second%20screen%3A%20%2Fboard%2F%3CBOARD_TOKEN%3E%3Fcolumns%3DReady.%0A%0ADEFINITION%20OF%20DONE%0A%0A-%20%5B%20%5D%20A%20signed%20order.created.v2%20creates%20a%20card%20in%20New%20within%205%20seconds%2C%20showing%20the%20code%2C%20items%20and%20a%20running%20timer.%0A-%20%5B%20%5D%20order.preparing%2C%20order.ready_for_pickup%2C%20order.completed%20for%20the%20same%20id%20move%20the%20card%20one%20column%20at%20a%20time%2C%20and%20it%20disappears%20DONE_MINUTES%20after%20completed.%0A-%20%5B%20%5D%20Receiving%20order.preparing%20after%20order.ready_for_pickup%20leaves%20the%20card%20in%20Ready%20\(it%20never%20goes%20backwards\).%0A-%20%5B%20%5D%20The%20same%20event%20delivered%20twice%20changes%20nothing%20the%20second%20time.%0A-%20%5B%20%5D%20A%20bad%20signature%20returns%20400%20and%20the%20board%20does%20not%20change.%0A-%20%5B%20%5D%20order.canceled%20removes%20the%20card%20immediately.%0A-%20%5B%20%5D%20%2Fboard%2F%3CBOARD_TOKEN%3E%3Fcolumns%3DReady%20shows%20only%20Ready%20cards%2C%20large%2C%20with%20no%20item%20list.%0A-%20%5B%20%5D%20%2Fboard%20and%20%2Fapi%2Fboard%20with%20a%20missing%20or%20wrong%20token%20return%20404.%0A-%20%5B%20%5D%20Neither%20the%20HTML%20nor%20the%20JSON%20contains%20an%20email%20address%2C%20phone%20number%2C%20surname%20or%20delivery%20address.%0A-%20%5B%20%5D%20Turning%20the%20backend%20off%20and%20on%20loses%20no%20cards.%0A%0AWHAT%20TO%20GIVE%20ME%20WHEN%20YOU%20ARE%20DONE%0A%0A1.%20The%20public%20webhook%20URL%20\(%E2%80%A6%2Fwebhooks%2Fstorekit\)%20and%20the%20full%20%2Fboard%20URL%20including%20the%20token.%0A2.%20The%20environment%20variables%20to%20set%20\(STOREKIT_WEBHOOK_SECRET%2C%20BOARD_TOKEN%2C%20TIMEZONE%2C%20optionally%20DONE_MINUTES%2C%20WARN_MINUTES%2C%20LATE_MINUTES\)%20and%20where%20to%20set%20them%20on%20this%20platform.%0A3.%20Confirmation%20that%20the%20backend%20stays%20up%2024%20hours%20a%20day%20and%20does%20not%20sleep%20between%20requests.%0A4.%20How%20to%20open%20the%20board%20in%20full-screen%20mode%20on%20a%20TV%20browser.%0A)

[![Open in Replit](https://replit.com/badge?caption=Open%20in%20Replit)](https://replit.com/?stack=Build\&prompt=JIAg7ghgdgLiEgDYEsBuBTEB7ATgE3RxACMsJ8QAzXEAWwE8Qd0BnGCAVx2hgC54qHRIgC0LAMbN0UcOmIgADhADmmGAAsIcFuqxgW2BdOz5CBiAfHk8B5DPFZEHWlHNQ8dLBgMb0teIhYUMrwBmy46ADWyHAw6MK2cBCkHNrsMByWmsGsAHQgAHJYpHiMdnHc4jAGYDHqIL4kZPgA3CAxIABWmWkQ9Obi4lxa6PnhzNHa0jYN6phsWpkg4tmq5jVyuliRLLkAUHsAQhzIiB4dtRoCxBDikdOzWiCa5oocxCjiIAASACq-AAUAMogACqACUADJUGiNMCbLDbEAACgICkCZSS7ga8UQdDUczBUIAlAAaAQsWgQYQgPBaZIWTDUIiNIY4ZiwEwEHAscnQDwISg4IJwB4EFjIZRQdAeZkCX4ANX2ewAwgB5Aq-ACiAA1fiAAIKHNWg-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-aPIus-c7YRIiA3-NEVLVQhAKwkoZ2Yqlxbfme0qjmAy1HrHNm7f4BHSDxFjuwFO4Tli6IOD-cp3dh7NOH0QHe5925Vs+I-bdT13i963Vtcqv58dh2p2PcJ8ui2W3ofLYog7Qa21ODcFgBVlgThlA9oYCIYs7BSxCYl0YvlAY7OA8nWwKQMBQ49ryyAYbc44XzeIibpKMA90UTt3x8nxFIQ9yCK752bA6uboosLUOBoQDB4NK7+ZsB1o9tBNaCPkboD0Ax9LxgQ0SwK7gJCGAj2XsOBUnYEYRn+BQ7zJ2DIAeQDh1Drke5RXxfEUCMEGIHAK+4dyIu0r7PAfc7Xa13j8nuW8v5YboXDvKHm4ooVmBEAV4GgwErqhjvnfEUmaXkAe6Jdgzoj2z37hvcS99zAf3sOtShxAAVg0BQKuR6oYhiiseKuGZpz1vjiJ8AF7iEXziDejbl-81XmvCOcOFiRvSCP-DiNvK2IrayBtHvCdOA7nbnPYQRQQGkFANeNxZOOiIwAVMlZtGYB8eAKcXVBQWScKcKeCGAJLcKMucQFgGg0iQIIwHkcKeEdcREHYVgw8SIEQPKA4X4b4LUOOX4A0X4UEEELUBULUTUIEA4Q4VgS6A6PtfjAdaya6eFFsDOGOHCHRPKFGXRWNUyExDoDhbwWYYUDgUbQ6SYJHKAfIPtSmSSdmYIckPtbxegQWZkQWFDKFKKdGGATw3AQWAfUFKKaMCtTJOMDqKtKMaACSJ+AIPETuAkQgykWRfZPofA8kQGNOXIPtSJMIShFORoaUMAQOc+VtQXEiZqGdGIxwpQdFVXADViekEfaDKtMdYiadCiWdedPLXHDdWHbdXdXHAonteo5w1XPiPnaQKqTo4XPbMXFiGHFbVPOXbXUYlXNXegDXdYjPIiLvdnJAoEBeStYxS6LVXIi4mxQkIKCmTIsgAUbEXIvta42EQkNInmbNaUWVcbFyJMIKVuaSBVJ4EmLgVwQ6C49MQYquMNemOoNyHQVVLRX4Q1VseI8hAwIiZgUwpkauJoawA41+VIQInAYIj9ZGIBIiQLDLQSMbX5deYCW4CSKSFKGgZgFWSIrSRxeMKtQOfoNhOhMohZU4cJAwRzNeAojxeAPabwZUVabMSwOmcgTZROfw7ALqfyRJGAWoCSfIOFCIAQBXFeZg8opYZEFQlKYnCYxo9fXOdqDwrwnwocJdVwkkrwkIy2YqSNBSSI+vcRDEjwH9F+bEaUCpXEgQXaDwJsAQQIeEIgY0x4GQDoakdwwBXYA4YWb4UQkAC0EAQ4UEYASEAFA4avV8d8fULg9go8CgjQjoQAFAIQB9Y-pGgFolopopRPpMBAY-JKAQYwYIYYZ4ZEZyAuFsQ+YrRbRfgRYxYJYpYZY5YFZp5Z415GhuAyjNoxo4BMI2hrY2cgxH4uAtF45E5Ghk4KEMgcAZBC42cxUsx6kRg5FjIFA2ICQnJmgPAz4f4KhIB8BY5gBAlJpE9To3ooSvEHTgw-F9hrJ8gYk2VKVOZLF0B6AUpiAFw6y4AtSLi5p+BBj1Cg5c5oBIhXCq1vkjBi4qVfVDDkQaBuNYxrMyoqAs414YdVkOsL5WKQBScy9QjgcDY3IYdyxDNYwtTDNipDUZRBNRksV1pFMKpZiqKsxyQfN9IXzLJczbibUwiJKQobJG18CmKEgml0AhRWA5gDB6Ful4g8A2hPi8Q8DHiDBsFXzsAoA6UCjCKoAV50wVKsE1K6124upT4qR7im1HiZTVSDo5Q6YtTGhkIKBsIwzESko55lgJLIzrBj56huhVzCQYSXp0glgWBgIOhCDR5t4qC9gl18gABxLUSs+KvAcKSiY0A0cEAFQWC0a0aQhiEARsxoO40QCQKQGQeJbZXNQgTAVq9qzqqWaQrVBAUApgfkOEjOGtOAIUOEumBmLOIIFmOANmTZSiukyarVdsJALASCGQTeKwEyDoJ0CE5BYMGtYwTePBaMvKkDCofVRoJse4ZMhVTAUCLVPyNyxgDWM1JKD0fBBHHIHkbAVIHkjaEaLclOcKRqxJcKVVZAdGj8rK9Us8stVWP64wVQHMa2C2KIxkiGkIeIDOHyD6tQbYYwQpFisJTNCyIgOcQIZQQMIgQgYUIgMa2OeQuM9ucAP4iKDGuUHQXAGANoakSAAUuwcQJwXqX65mmQDOZQfak6jMBJVmuAUeFWtWrmUNRi+KK82NdBdLbZMLUUmgT0vXflNWCa45QG-WytMUvqCbTc7aWOOrJwFwcsBSX+FOTZUbPgQodAMoi0nS2MAEZxSY8kIaIxRgZEdw0k7wiFbBWMGkzAC0n0yhGUN0gIj0ik+gYkfIXFP84MXQMAOSQkLUmE6VPMNbbZGDIwKm9gRCzi1kc2wgAAcnFOYqNutshI63hjXipIbQAHWrcRsxtPRJsZs5ssqdl+L4a7ACBYByFQz0lbBkyBlQZyx0wUExVET67VwLlrVP8ppNE0ra1JKqCQAa6ZhyAzjJqOozhtomLs5upFD6ZrxlhHBnA7DX7rBD6QB87aQHwObyBoGNQtRBY7ZgAChTQtQQRuE3JfBHZd66loHv4UQCAsohA4BiMq6IHa65QAz4g2Sq0DAgN4GubaB7zwY4g5F9gLY9axqPZTKdADoYhGH6RD5kZI41qe5g4NB1TKBPh7giBkRKBKED5fx-xNr-A6rKzsbcbrBmrpqOquqeqqbmBdj3A0FEkTIM5SlCAxAj48pKHgAgq+Hix6hH5yx7hJI669BNZCRfK04qbZayjgrZFiJmBo5pQqhUwDi8wLM2gkqiBAnrg8wvKCT8B9hw5BIL6iAHBAguBZJaQTLOBEB9Iyc8RhY2qCgUG0GMGQRURCmyHQ8rZYxhF00sFSmQBIRRDkHUH0HtRamSGim4BC5YwCHsGzIQBJkaB-IpH6gEAQjFM2EBrUgjzvSFlaQ9BYJgJ5mL7uo4gjEqaYn4hwliI5RQ4tMbYiJ9gCt8ggUJQpR9CEBiMSsrYhxFQmz9p4RZR8SNApB8RF98m6QAEbg7hlALD3ByQb6xVnB18SsFAA5MIcQGoSLjFBjzA4AFILA4Af0Cs4XyRzrdA1oRBWE7ACwjYgg8XgJhrHBEB6wDBGyuxIzQGXBylCBZG9BywdB0UV4oyDBQXbgTKbUxzPz67iJMsChPBmAiTRx3zdUw19git8hJlTgKg9ZwYuKqpkYTdVxirxbxFFAXZORdklGORtAXqISngjXJrWYPn8mAB+bA1gAAXgJUhRGXg2lUCdpXBrdE-pwDItYDaFtZybAZYEddTp8Xrq9ceB2iZZkEBkKQPmIElFUGycgYeZDVKhsayjBkJmxIgpjJyYUiqAOOGpSsDf50dcomV0sN6oZcaGZK+vTIlYJBiqTH532BK1qvqoikMWKfqC1jgDBKvIMBvKpqh22BPSaz4ipr0jwVpsQAzhWmlQ7DGyYH8MwENp9XhN8CIB0fwD0bVDaoMbmoKAYnJCxo9D3aapasPZmsMdPe1MXm4OVDtgAE18zb2SyKVFMptv5+BfhgA7YtRKMkGklGY9qj5DqOYFJbKRWQnUEoGaEKUgQRXGgnUp7ML7BIH+AUKMyLxEVM2cARBs3VwoOeBxbyY8QHrXKiZo5PyzX+Ar2D2j3ZrurT2g3Y3Q3w3yU9gAUtRJk0G7RgB-w1RJlEGCgtQDgLwXUQA3VDQOyZptKn7dKG1LTXgozBSY6sq5xxGvJJHror7CZWQsACA2K-ABUnFnQoBNYsmBoZO5OHCk7GjkXCBooHSs7nSXPq0i7IibihERELiIzWRMqiHg3mWnhz6lKqaEI4GZxyADAAUkGqnenMH4Bf43JwjfSZQ7PZPZcJI0BVxHOnDbSxm0p3OnSc6hwMWzDguKA5xuOUQOh4mQBlAgsDBgXIhfybAq6KV7ONL-PMBk4B8UodStllhVgDpZ2fH5g9PfIL6cu5OQ8bhPyPpLzMAh2IS9zu6MI8ay5JuOxxusZRhevcvlC4i6GPAcSvADpavzhWGZR2H4hyVpPcumOb2WP72GIOOg7XAw2ILPXXK6UGueXlKRtxqvxzr0P0DKqXu5Or2qaL2caMafZjVclCuiAwAmYQgSaZANvLYrZuGTu5O6EESWRCQ-g7ZoR2wyfMBVHxXo52BWp3F7a8RnaAwbaiHrUONzHLaYdEknah8AwFvczwTpuSBbh-qOo+yqagxt59uKw-zlQsycy8yargApCrchCsz5rX2TRDQrRxPJO9gyyUCCoPhQY1wNwiRoRkRAAyAirI3HoMoJDMZtQLxHh+BpNpbzF5x+gr1p2qZn2qbPZkop8EpaKORAnIFmnKYznOlllnlnqvJH0dY+kPJAA6A5A4k9cKkmkVkUYES4k+S5qfJHKfBEqZ6ZL46a6eL76Zd5CVOpjIzmlf8CDA1nylLwLH2GqtfqCEcypGBOjcOg64eAWAFKJWw2eHRnhrmb6Cpr24MFHmVyhp9R1JSo9i2nGi4fyG3DKJjKwCvmH7xrnEGrEDNZ2X+PlAVBIGFH0Fc72CAA\&referrer=docs.storekit.com)

Either button opens the builder with the whole prompt below already filled in. Nothing starts until you press send, so read it through and edit the parts marked as yours first.

> **Volledige prompt (klik om uit te klappen)**
>
> ```text theme={null}
> I want a live order board for my restaurant: a full-screen web page that shows open orders as cards in columns and moves them along as storekit tells it about status changes. Nobody interacts with the board; it just stays accurate. storekit sends the status changes as webhooks.
>
> Build it with a backend that has a public HTTPS URL for the webhook (deploy it and tell me the URL), a small database for the current orders, and a front end designed for a TV.
>
> ```
>
> ```text theme={null}
> CONTEXT ABOUT STOREKIT
>
> I run a hospitality business on storekit (storekit.com), an online ordering and payments platform for restaurants, cafés and bars. storekit can notify my own app about things that happen in my store by sending webhooks.
>
> How storekit webhooks work:
> - Each event is an HTTP POST with a JSON body to a URL I register in the storekit dashboard.
> - The body has the shape { "event": "<event type>", "data": { ... } }. Two exceptions: the legacy order.created event (do not use it; use order.created.v2) sends the bare order object, and the order.refund.created / order.refund.succeeded / order.refund.failed events put order, customer, venue and refund next to event instead of inside data.
> - Requests are signed with Svix. Every request carries the headers svix-id, svix-timestamp and svix-signature. Verify them with the official `svix` library for your language, using a secret I will provide in an environment variable called STOREKIT_WEBHOOK_SECRET. Reject anything that fails verification with HTTP 400.
> - My endpoint must respond with a 2xx status within 15 seconds. Save the event to the database first, respond 200, then do the work from the saved copy.
> - Once I have responded 200, storekit will not send that event again, so the work must not be lost: store each event with a status (pending, done, failed) and only mark it done after the work has actually succeeded. On startup and on a timer, pick up anything still pending or failed and retry it. Never do the work only in memory after responding.
> - The same event can occasionally be delivered more than once. Use the svix-id header as the unique key for the saved event: a repeat of an event already saved is ignored, whatever its status.
> - Any page or endpoint protected by a password must be served over HTTPS only; redirect or refuse plain HTTP.
> - Money fields (total, tip, deliveryFee, discountTotal, item price, modifier price) are integers in minor units: 2500 means £25.00.
> - orderType is one of Delivery, Pickup, InStore, Curbside, CateringDelivery, CateringPickup or Billpay.
> - table is only present for in-store orders; deliveryAddress and pickUpTime only for delivery orders. When they do not apply the key is omitted, so treat a missing key the same as null. notes and items[].note can be null or empty. customer.companyName and customer.vatNumber appear only when supplied.
>
> Example order.created.v2 payload:
>
> {
>   "event": "order.created.v2",
>   "data": {
>     "id": "ord_abc123",
>     "code": "A1B2",
>     "asap": true,
>     "total": 2500,
>     "tip": 250,
>     "deliveryFee": 299,
>     "discountTotal": 0,
>     "orderType": "Delivery",
>     "pickUpTime": "2024-01-15T10:45:00Z",
>     "channel": "WEB",
>     "poNumber": null,
>     "createdAt": "2024-01-15T10:30:00Z",
>     "deliveryTime": "2024-01-15T11:00:00Z",
>     "notes": "Ring doorbell",
>     "customer": {
>       "firstName": "John",
>       "lastName": "Doe",
>       "email": "john@example.com",
>       "phone": "+44123456789",
>       "marketingConsent": true
>     },
>     "items": [
>       {
>         "name": "Margherita Pizza",
>         "printName": "Margherita",
>         "price": 1200,
>         "quantity": 1,
>         "plu": null,
>         "posId": null,
>         "taxRate": 20,
>         "note": null,
>         "modifiers": [
>           { "name": "Extra cheese", "printName": "Cheese", "price": 150, "quantity": 1, "plu": null, "posId": null, "taxRate": 20 }
>         ]
>       }
>     ],
>     "venue": {
>       "id": 1234,
>       "name": "My Restaurant",
>       "slug": "my-restaurant",
>       "address": {
>         "street1": "123 Main St",
>         "street2": "",
>         "city": "London",
>         "postCode": "W1A 1AA",
>         "country": "UK",
>         "companyName": "My Restaurant Ltd",
>         "coordinates": { "latitude": 51.5074, "longitude": -0.1278 }
>       }
>     },
>     "deliveryAddress": {
>       "street1": "456 Oak Ave",
>       "street2": "Flat 2",
>       "city": "London",
>       "postCode": "E1 6AN",
>       "country": "UK",
>       "notes": null,
>       "coordinates": { "latitude": 51.5155, "longitude": -0.0722 }
>     }
>   }
> }
>
> The full list of event types and payloads is at https://storekit.com/docs/developers/webhooks/webhook-events
> ```
>
> ```text theme={null}
> THE STATUS EVENTS
>
> Besides order.created.v2, storekit sends these events for the same order as it moves through the kitchen. order.preparing, order.ready_for_pickup, order.out_for_delivery, order.completed and order.canceled all have this smaller payload, with data.order.status set to the new status:
>
> {
>   "event": "order.preparing",
>   "data": {
>     "order": { "id": "ord_abc123", "code": "A1B2", "status": "preparing" },
>     "venue": { "id": 1234, "name": "My Restaurant", "slug": "my-restaurant" }
>   }
> }
>
> So the order id is data.id for the full payload and data.order.id for the smaller one. Write one function that returns the id and code from either shape. Treat canceled as "remove from board" and out_for_delivery as "Done". There is no accepted or rejected event: an order stays in New until its first status event arrives.
>
> Events can arrive out of order or twice. Store a rank per status (created 0, preparing 2, ready_for_pickup 3, out_for_delivery 4, completed 4, canceled 9) and never move a card to a lower rank than it already has.
>
> WHAT TO BUILD
>
> 1. POST /webhooks/storekit — verify the Svix signature with the official svix library and STOREKIT_WEBHOOK_SECRET against the raw request body; 400 on failure. Save the event and return 200 immediately; update the board state afterwards. Ignore any svix-id already saved.
> 2. An orders table keyed by storekit order id with: code, status, rank, orderType, table name (or null), customer first name, items (name, quantity, modifiers names, notes), order notes, createdAt, deliveryTime, venue name, last updated. The full order.created.v2 payload fills or refreshes every field; small payloads update only status, rank and last updated. If a small payload arrives for an order the board has never seen, create a card with just the code and status so it is not lost.
> 3. GET /board/<BOARD_TOKEN> — the full-screen page, where BOARD_TOKEN is a long random secret from an environment variable. There is no login because it runs on a screen behind the counter, so the token in the URL is the only thing keeping strangers out: any request to /board or /api/board without the exact token gets 404 and nothing else, and the token must never appear in logs or error pages. Below I write /board for short; always include the token segment. The page must not include customer surname, phone, email or delivery address anywhere in the page or its data requests. Columns, left to right: New (created), Preparing, Ready (ready_for_pickup), Done (completed, out_for_delivery). Cards show: the order code very large, orderType and table name, the customer's first name, the item list as "2 × Margherita Pizza" with modifiers indented and notes in italics, and a timer showing minutes since createdAt. Cards are ordered oldest first inside each column. Cards in Done disappear DONE_MINUTES after they entered Done (default 10). Cards for canceled orders disappear immediately.
> 4. The page refreshes its data every 5 seconds without flicker (fetch JSON from GET /api/board/<BOARD_TOKEN> and re-render, or use server-sent events). If a refresh fails, keep showing the last data and show a small "reconnecting" label; never show a blank board.
> 5. Timer colours: default until WARN_MINUTES (default 10), amber until LATE_MINUTES (default 20), red after. For orders with a deliveryTime in the future, count down to deliveryTime instead and label it "for 11:00".
> 6. Design for a 1080p TV viewed from three metres: dark background, minimum 28px body text, order codes at least 96px, no hover-only information, no scrollbars — if a column overflows, shrink cards gracefully and show "+N more" at the bottom.
> 7. Filters via query string so I can point different screens at different views: ?types=Pickup,Delivery shows only those orderTypes; ?columns=Ready shows only that column with much bigger cards for a customer-facing "ready to collect" screen; ?venue=<slug> if the account has more than one venue.
> 8. GET /health that returns 200 and { "ok": true } and nothing else. Every other route must be either /board/<BOARD_TOKEN>, /api/board/<BOARD_TOKEN> or the webhook.
>
> MY BOARD
>
> - Timezone: TIMEZONE environment variable; show all times in it.
> - Show the item list on cards: yes.
> - Customer-facing variant I will run on a second screen: /board/<BOARD_TOKEN>?columns=Ready.
>
> DEFINITION OF DONE
>
> - [ ] A signed order.created.v2 creates a card in New within 5 seconds, showing the code, items and a running timer.
> - [ ] order.preparing, order.ready_for_pickup, order.completed for the same id move the card one column at a time, and it disappears DONE_MINUTES after completed.
> - [ ] Receiving order.preparing after order.ready_for_pickup leaves the card in Ready (it never goes backwards).
> - [ ] The same event delivered twice changes nothing the second time.
> - [ ] A bad signature returns 400 and the board does not change.
> - [ ] order.canceled removes the card immediately.
> - [ ] /board/<BOARD_TOKEN>?columns=Ready shows only Ready cards, large, with no item list.
> - [ ] /board and /api/board with a missing or wrong token return 404.
> - [ ] Neither the HTML nor the JSON contains an email address, phone number, surname or delivery address.
> - [ ] Turning the backend off and on loses no cards.
>
> WHAT TO GIVE ME WHEN YOU ARE DONE
>
> 1. The public webhook URL (…/webhooks/storekit) and the full /board URL including the token.
> 2. The environment variables to set (STOREKIT_WEBHOOK_SECRET, BOARD_TOKEN, TIMEZONE, optionally DONE_MINUTES, WARN_MINUTES, LATE_MINUTES) and where to set them on this platform.
> 3. Confirmation that the backend stays up 24 hours a day and does not sleep between requests.
> 4. How to open the board in full-screen mode on a TV browser.
> ```

## Stap 2: Verbind storekit

**1. Open the webhooks portal**

In the dashboard sidebar, go to **Settings** → **Webhooks**. If your plan includes webhooks and they are not yet enabled, click **Enable Webhooks**. If the page shows **Upgrade to enable webhooks**, [contact support](/docs/getting-started/contact-support) to discuss adding webhooks to your plan.

**2. Add your app as an endpoint**

In the embedded portal, add a new endpoint, paste the webhook URL your app builder gave you, and tick only the event types the recipe needs.

**3. Copy the signing secret into your app**

Open the endpoint you just created and copy its signing secret. In your app builder, save it as the environment variable `STOREKIT_WEBHOOK_SECRET` (builders call this "secrets" or "environment variables"). Redeploy if the builder asks you to.

**4. Send a test event**

Use the portal's test option on the endpoint to send a sample event. The delivery log shows the response code from your app. A `2xx` means your app accepted it; anything else, read the response body in the log — it is usually a missing secret.

Vink voor dit endpoint `order.created.v2`, `order.preparing`, `order.ready_for_pickup`, `order.out_for_delivery`, `order.completed` en `order.canceled` aan.

## Stap 3: Test met een echte bestelling

Open de bord-URL (met de token) op het scherm en plaats vervolgens een bestelling op je eigen storefront. De kaart verschijnt in New; loop de bestelling door zijn statussen in het dashboard (zie [Live bestellingen beheren](/docs/nl/guides/orders/managing-live-orders)) en zie de kaart volgen. Annuleer een testbestelling om te zien dat deze verdwijnt.

Het accepteren van een bestelling stuurt geen webhook, dus de kaart blijft in New totdat de keuken hem als preparing of ready markeert.

## Ideeën

* **Geluiden**: een tune wanneer een kaart in New komt, een andere wanneer deze in Ready komt.
* **Stationborden**: tag items per categorie (de builder kan matchen op itemnaam of `plu`) en toon één bord per station, zoals het [aangepaste keukenticket](/docs/nl/guides/build/recipes/custom-kitchen-ticket) op papier doet.
* **Late-order-melding**: post naar Slack wanneer een kaart rood wordt, met het patroon uit [Ordermeldingen](/docs/nl/guides/build/recipes/order-alerts).

## Wat dit niet doet

* **Het kan een bestelling niet wijzigen.** Het bord is read-only; accepteren, klaarmelden en afronden gebeuren nog steeds in het dashboard of je kassa, waar de ingebouwde [Kitchen Display](/docs/nl/guides/orders/kitchen-display) voor bedoeld is.
* **Het weet alleen wat de events zeggen.** Er is geen `order.accepted`-event, dus een kaart blijft in New totdat de keuken hem als preparing of ready markeert. Een afgewezen bestelling stuurt ook geen event; deze beweegt simpelweg nooit. Als een kaart niet beweegt, controleer dan het webhook-leveringslog in de portal om te zien wat er is verzonden.
* **De URL is de sleutel.** Iedereen met de volledige bord-URL kan live ordercodes, voornamen en items zien, dus behandel deze als een wachtwoord: deel hem niet buiten het team en wijzig BOARD\_TOKEN als een scherm kwijtraakt.
* **Het begint leeg.** Bestellingen die vóór het verbinden van het endpoint zijn geplaatst, verschijnen niet.
* **Het is jouw service.** storekit-support kan bevestigen dat de events je URL hebben bereikt en de responscode; het bord is van jou.

## Gerelateerd

> **[Order-events](/docs/nl/developers/webhooks/webhook-events#order-events)**
>
> Elk lifecycle-event en payload voor bestellingen.

> **[Kitchen Display](/docs/nl/guides/orders/kitchen-display)**
>
> Storekits ingebouwde bumpbare keukenscherm.

> **[Orderstatussen](/docs/nl/guides/orders/order-statuses)**
>
> Wat elke status betekent en hoe bestellingen ertussen bewegen.

> **[Een prompt schrijven die werkt](/docs/nl/guides/build/prompting-guide)**
>
> De structuur achter deze prompt, en wat te zeggen als de builder het mis heeft.


## Related topics

- [Bouw je eigen tools op storekit](/docs/nl/guides/build/overview.md)
- [Live-bestellingen beheren](/docs/nl/guides/orders/managing-live-orders.md)
- [7. Testen en live gaan](/docs/nl/getting-started/launch/test-and-go-live.md)
- [Custom keukenbon op een Star cloud printer](/docs/nl/guides/build/recipes/custom-kitchen-ticket.md)
- [Livechatwidgets](/docs/nl/guides/increasing-sales/chat-widgets.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.