Integraties / Medusa

Medusa

Medusa blijft je centrale bron. Tender POS spiegelt catalogus, voorraad, klanten, promoties, locaties en bestellingen voor snel scannen en schrijft kassaverkopen en ondersteunde terugbetalingen terug naar Medusa.

CategorieE-commerceplatform
Eerste synchronisatieGepagineerde Admin API
Actueel viaOndertekende companion-events + volledige sync

Zo koppelt Tender POS met Medusa

Koppel met een publieke backend-URL en geheim Admin API-token. Voor vrijwel realtime updates is de Tender POS-backendcompanion in je Medusa-app nodig.

01

Koppel de backend

Voer de Medusa API-URL, het geheime Admin API-token en het webhooksecret in. Medusa gebruikt geen OAuth-scopes.

02

Installeer de backendcompanion

Deploy de Tender POS-subscriberbestanden in je Medusa-app en stel de POS_WEBHOOK_*-omgevingsvariabelen in. Volledige synchronisatie werkt zonder deze bestanden; vrijwel realtime updates niet.

03

Start een volledige synchronisatie

Tender POS leest locaties, catalogus, voorraad, klanten, promoties en bestellingen via gepagineerde Admin API-calls. Medusa heeft geen bulkexport-API.

Wat tussen Medusa en je kassa wordt uitgewisseld.

Gepagineerde Admin API-calls bouwen de lokale spiegel op. Ondertekende companion-events houden ondersteunde gegevens actueel; volledige synchronisatie verwerkt promoties, voorraadlocaties en gemiste events. Workflows schrijven ondersteunde verkopen en terugbetalingen terug.

Gesynchroniseerd naar je kassa

Producten en varianten

Titels, omschrijvingen, SKU's, barcodes, opties, afbeeldingen en ondersteunde voorraadkoppelingen. Een gevolgde variant moet aan één voorraaditem met aantal één gekoppeld zijn.

Variantprijzen

Valutabewuste variantprijzen uit Medusa-prijslijsten en -regio's.

Voorraad per locatie

Beschikbare voorraad per voorraadlocatie voor ondersteunde gevolgde varianten met één component. Niet-ondersteunde voorraadkits stoppen veilig in plaats van een onjuist voorraadniveau te gebruiken.

Klanten en adressen

Namen, e-mailadressen, telefoonnummers, opgeslagen adressen en metadata.

Bestelgeschiedenis

Totalen en statussen, klanten, locaties, orderregels, betalingen, fulfilment en terugbetalingen.

Promoties en codes

Actieve kortingscodes met een vast bedrag of percentage die Tender POS ondersteunt. Andere promotievormen blijven in Medusa.

Voorraadlocaties

Medusa-voorraadlocaties, adresgegevens en de gekoppelde verkoopkanaalcontext.

Teruggestuurd naar Medusa

Winkelverkopen

Volledig of gedeeltelijk betaalde kassaverkopen worden Medusa-bestellingen via een herstelbare workflow van concept naar bestelling, inclusief betaal- en afrondingsstatus.

Klantgegevens

Tijdens het opbouwen van een nieuwe kassabestelling kan een klant worden gekozen of aangemaakt. Tender POS wijzigt de klant van een bestaande Medusa-bestelling niet.

Terugbetalingen

Tender POS betaalt een bedrag terug op één vastgelegde Medusa-betaling. De verdeling over orderregels blijft in Tender POS; terugbetaling per orderregel en herbevoorrading in Medusa worden niet ondersteund.

Voorraad na verkoop

Voor ondersteunde gekoppelde varianten verwerkt Medusa de normale voorraadbeweging wanneer de kassabestelling wordt aangemaakt.

Scannen wacht niet op Medusa.

Een barcode wordt opgezocht in de lokale spiegel van Tender POS in plaats van via de Medusa-API, dus een scan is binnen 100 ms klaar, ook als de backend traag is. Workflows proberen verkopen en terugbetalingen opnieuw zodra Medusa weer bereikbaar is.

Scanlokale spiegel · 68 ms
Linen Overshirt — M8712345 678904 · Amsterdam€89,95
Op voorraad · 4 hier2 Rotterdam · 1 incoming
Toevoegen aan verkoop
Developer guideGeverifieerd tegen Medusa 2.13.6

Installeer de webhook-subscriber

Voor de developer die je Medusa-backend beheert. Dit is een aanvullende module die je in je eigen Medusa-applicatie draait.

Medusa biedt, anders dan Shopify, geen webhooks via zijn Admin API. Daardoor heeft Tender POS geen andere manier om te weten dat je catalogus, voorraad, bestellingen of klanten zijn gewijzigd. Zonder deze code in je app ziet Tender POS wijzigingen pas bij de volgende volledige synchronisatie — niet vrijwel realtime.

Versiepin

Geschreven en geverifieerd tegen Medusa 2.13.6 — @medusajs/framework, @medusajs/medusa, @medusajs/utils en @medusajs/core-flows, allemaal op 2.13.6. Draai je een andere minor-versie, controleer dan eerst de event-namen hieronder — Medusa heeft eventconstantes in het verleden hernoemd tussen versies.

Wat het installeert

Negen subscriber-bestanden plus één gedeelde helper. Geen nieuwe dependencies, geen migraties, geen nieuwe API-routes in je Medusa-app.

your-medusa-app/src
src/
├── lib/
│   └── tender-pos-webhook.ts          # signs and sends every delivery
└── subscribers/
    ├── product.ts                     # product.created/updated/deleted
    ├── product-variant.ts             # product-variant.created/updated/deleted
    ├── product-option.ts              # product-option.created/updated/deleted
    ├── inventory-item.ts              # inventory.inventory-item.created/updated/deleted
    ├── inventory-level.ts             # inventory.inventory-level.created/updated/deleted
    ├── order.ts                       # order + order-edit lifecycle events
    ├── payment.ts                     # payment.captured/refunded
    ├── fulfillment.ts                 # shipment.created/delivery.created
    └── customer.ts                    # customer.created/updated/deleted

Installatiestappen

01

Kopieer de bestanden

Download het subscriber-bronarchief en pak het uit. Kopieer daarna webhook-subscriber/src/ naar de src/ van je Medusa-project, met behoud van de bovenstaande relatieve paden.

shell
cp -R webhook-subscriber/src/lib/tender-pos-webhook.ts \
  your-medusa-app/src/lib/
cp -R webhook-subscriber/src/subscribers/*.ts \
  your-medusa-app/src/subscribers/

Heb je al een src/subscribers/product.ts? Hernoem die van ons naar tender-pos-product.ts — Medusa laadt elk bestand in die map, ongeacht de naam.

02

Stel de omgevingsvariabelen in

Voeg deze toe waar je Medusa-proces zijn omgeving leest — de omgevingsvariabelen-instellingen van je host, een .env-bestand dat je process manager laadt, of de eigen configuratie van je containerplatform. Sommige hosts herstarten het proces automatisch na een wijziging; andere hebben een expliciete redeploy nodig.

POS_WEBHOOK_URLVerplicht

De volledige URL van het Medusa-webhookendpoint van Tender POS: https://api.tenderpos.io/webhooks/commerce/medusa. Vraag je Tender POS-contactpersoon om de actuele waarde te bevestigen.

POS_WEBHOOK_SECRETVerplicht

Exact dezelfde waarde die is ingevoerd als “Webhook signing secret” toen Medusa werd gekoppeld in het Tender POS-dashboard. Komen deze twee niet letter voor letter overeen, dan wordt elke delivery met een 401 geweigerd.

POS_WEBHOOK_ACCOUNT_HANDLEAlleen indien nodig

Overschrijft de account-handle-header. Laat dit leeg, tenzij de vorige stap iets anders aangeeft.

03

Controleer de account-handle

Tender POS koppelt een delivery aan jouw koppeling via de host van de publieke URL van je Medusa-backend — dezelfde waarde die als Medusa API-URL is opgeslagen toen je koppelde. De helper berekent dit automatisch wanneer je omgeving die URL al beschikbaar stelt als BACKEND_PUBLIC_URL; stel anders expliciet POS_WEBHOOK_ACCOUNT_HANDLE in.

handle = alleen de host, geen scheme of pad
# Medusa API URL entered in Tender POS
https://medusa.example.com

# resulting account handle
medusa.example.com
Stille fout
Een verkeerde handle geeft hier geen foutmelding. pos-api logt ignored_unknown_account en negeert de delivery, omdat een onbekende handle er hetzelfde uitziet als “deze winkelier heeft de subscriber nog niet geïnstalleerd”. Verlaten deliveries je app zonder dat er iets verandert in Tender POS, controleer dit dan eerst.
04

Deploy, en controleer of het werkt

Herstart de app zodat de subscribers zich registreren. Medusa logt elke subscriber bij het opstarten — zoek naar de tender-pos-*-subscriber-id's. Wijzig daarna de titel van een product in Medusa Admin om product.updated te laten afgaan. Bij succes verschijnt er bewust niets in de logs.

wat de logs je kunnen vertellen
[tender-pos-webhook] POS_WEBHOOK_URL / POS_WEBHOOK_SECRET not set — skipping product.updated delivery
  → set both in your Medusa environment (step 02)

[tender-pos-webhook] could not determine an account handle for product.updated
  → set POS_WEBHOOK_ACCOUNT_HANDLE explicitly (step 03)

[tender-pos-webhook] product.updated rejected with 401
  → POS_WEBHOOK_SECRET doesn't match, or an outdated subscriber is installed

[tender-pos-webhook] gave up delivering product.updated after 3 attempts
  → all 3 attempts failed; the next full sync catches it up
Vereist een worker- of shared-proces
Subscribers draaien alleen in een proces met worker- of shared MEDUSA_WORKER_MODE. Eén shared-modus-deployment (de standaard) dekt dit al. Splitst jouw deployment zich op in aparte server- en worker-mode-instances, zet deze bestanden en de POS_WEBHOOK_*-variabelen dan op de instance die in worker- of shared-modus draait — zie de backend-setup-handleiding voor die opsplitsing.

Events waarnaar geluisterd wordt

Elke naam hieronder is gecontroleerd tegen de stringconstantes in de geïnstalleerde @medusajs/utils@2.13.6, niet tegen de publieke documentatie — die loopt achter op releases.

product.ts
product.created · product.updated · product.deletedProductWorkflowEvents
product-variant.ts
product-variant.created · .updated · .deletedProductVariantWorkflowEvents
product-option.ts
product-option.created · .updated · .deletedProductOptionWorkflowEvents
inventory-item.ts
inventory.inventory-item.created · .updated · .deletedInventoryEvents
inventory-level.ts
inventory.inventory-level.created · .updated · .deletedInventoryEvents
order.ts
order.placed · .updated · .canceled · .completed · .archived · .fulfillment_created · .fulfillment_canceled · .return_requested · .return_received · .claim_created · .exchange_created · .transfer_requested · order-edit.requested · .confirmed · .canceledOrderWorkflowEvents, OrderEditWorkflowEvents
payment.ts
payment.captured · payment.refundedPaymentWorkflowEvents
fulfillment.ts
shipment.created · delivery.createdFulfillmentWorkflowEvents
customer.ts
customer.created · customer.updated · customer.deletedCustomerWorkflowEvents
Als je dit uitbreidt

Event-payloads bevatten alleen een id — nooit het volledige record. product-variant.ts en inventory-level.ts doen daarom een extra query.graph()-opzoeking, omdat pos-api het bijbehorende product-id van de variant en de locatie- en item-id's van het voorraadniveau nodig heeft.

Voeg inventory-item en inventory-level niet samen: een item-event betekent dat de eigenschappen van het item zelf zijn gewijzigd en is niet locatiegebonden, terwijl een level-event de voorraad op één locatie betreft. De spiegel verwerkt ze op een andere manier.

Ondertekening & retries

Elke delivery is een POST naar POS_WEBHOOK_URL, ondertekend over een canonieke payload — niet alleen de ruwe body.

request
POST /webhooks/commerce/medusa
content-type:                application/json
x-medusa-hmac-sha256:        <base64 HMAC-SHA256 of the canonical payload below>
x-medusa-signature-version:  1
x-medusa-account-handle:     medusa.example.com
x-medusa-topic:              product.updated
x-medusa-webhook-id:         3f6c1e8a-9d2b-4e51-8a6f-7c9d2e4b1a03
x-medusa-triggered-at:       2026-07-28T09:12:44.118Z

{
  "id": "3f6c1e8a-9d2b-4e51-8a6f-7c9d2e4b1a03",
  "topic": "product.updated",
  "createdAt": "2026-07-28T09:12:44.118Z",
  "data": { "id": "prod_01J..." }
}

De HMAC dekt deze regel-voor-regel opgebouwde string, niet alleen de ruwe request-body — de account-handle, topic, webhook-id en timestamp maken allemaal deel uit van de handtekening, naast een digest van de body.

de exacte bytes die worden ondertekend met HMAC
tender-pos-medusa-webhook/v1
medusa.example.com
product.updated
3f6c1e8a-9d2b-4e51-8a6f-7c9d2e4b1a03
2026-07-28T09:12:44.118Z
<sha256 hex digest of the raw request body>

5 minuten replay-venster

pos-api controleert of x-medusa-triggered-at binnen vijf minuten van de servertijd valt, bovenop de HMAC-controle. Retries hergebruiken de timestamp van de eerste poging, dus een zeer trage retry zou als verlopen worden geweigerd — in de praktijk zijn retries binnen enkele seconden klaar.

3 pogingen, dan laten vallen

De Redis-eventbus van Medusa gebruikt standaard attempts: 1, dus een subscriber die een fout gooit, wordt niet opnieuw geprobeerd door Medusa zelf. De helper doet de retries in plaats daarvan binnen de HTTP-call — met 250 ms en daarna 750 ms ertussen — en geeft direct op bij een 401. Hij gooit nooit een fout terug, zodat een storing bij Tender POS nooit het opslaan van een product of order kan laten mislukken.

Problemen oplossen

Helemaal geen tender-pos-*-regels in het opstartlogDe bestanden staan niet in src/subscribers/, of een typefout voorkomt dat de app opstart. Controleer de paden uit stap 1 opnieuw en draai de TypeScript-check van je project — een standaard Medusa-project gebruikt npm, yarn of pnpm, geen Bun, dus meestal is dat npx tsc --noEmit.
Geen tender-pos-*-regels in het opstartlog, terwijl de bestanden aanwezig zijn en verder niets vreemds opvaltDeze instance draait in server-modus — subscribers worden alleen uitgevoerd in worker- of shared-modus. Zet deze bestanden en de POS_WEBHOOK_*-variabelen op de instance die in worker- of shared-modus draait.
POS_WEBHOOK_URL / POS_WEBHOOK_SECRET not set in de logsDe omgevingsvariabelen zijn niet zichtbaar voor het draaiende proces. Controleer of ze zijn ingesteld op de service die daadwerkelijk draait — hosts met aparte preview- of staging-omgevingen zijn een bekende plek om de variabele op de verkeerde te zetten.
could not determine an account handleDe omgeving stelt geen publieke-URL-variabele beschikbaar (BACKEND_PUBLIC_URL). Stel expliciet POS_WEBHOOK_ACCOUNT_HANDLE in.
Elke keer rejected with 401Het secret komt niet overeen, of deze app draait nog een verouderde subscriber. Kopieer het secret opnieuw vanuit het Tender POS-dashboard zonder overtollige spaties, en installeer de actuele tender-pos-webhook.ts opnieuw.
Deliveries lukken hier, maar er verandert niets in Tender POSVrijwel altijd een mismatch in de account-handle — een onherkende handle wordt stil genegeerd in plaats van als fout behandeld. Controleer de handle nauwkeurig tegen de host van de Medusa API-URL van de koppeling.
Root-events werken, maar deletes van option, variant of inventory-level nietDe query.graph()-opzoeking kon het al zacht verwijderde kindobject niet vinden. Controleer of de geïnstalleerde subscriber withDeleted: true bevat, en bekijk daarna het Medusa-log voor het ontbrekende id.
Payment- of shipment-events komen binnen, maar de order wordt niet bijgewerktDe relatie-opzoeking kon payment_collection.order of fulfillment.order niet vinden. Controleer of het event bij een order hoort en of de huidige subscriber is geïnstalleerd.

Bewust eenvoudig gehouden: geen dead-letter queue of outbox-tabel, geen overgangsperiode voor het roteren van de signature, geen batching. Gemiste events worden bij de volgende volledige synchronisatie alsnog opgehaald, niet in je app zelf in een wachtrij gezet namens Tender POS.

Vragen over Medusa en Tender POS.

Blijft Medusa de centrale bron?

Ja. Beheer catalogusgegevens, prijzen, voorraad, promoties en online bestellingen in Medusa. Tender POS spiegelt de ondersteunde gegevens voor de kassa en schrijft nieuwe kassabestellingen en ondersteunde terugbetalingen terug. Terugbetaling per orderregel, herbevoorrading na een terugbetaling, cadeaubonnen en voorraadkits met meerdere componenten worden niet ondersteund.

Wat gebeurt er als Medusa traag of niet bereikbaar is?

Barcodes worden in de lokale Tender POS-spiegel opgezocht, zodat scannen niet op Medusa wacht. Voor het terugschrijven van een verkoop of terugbetaling moet de backend wel bereikbaar zijn; de workflow probeert dit zo nodig opnieuw.

Hoe komen verkopen terug in Medusa?

Tender POS maakt een Medusa-conceptbestelling, controleert het door Medusa berekende totaal, zet deze om naar een bestelling, legt de betaling vast en rondt de bestelling af wanneer die volledig is betaald. De workflow gebruikt duurzame markeringen, zodat een nieuwe poging geen dubbele bestelling aanmaakt.

Hoe blijft de lokale spiegel actueel?

De eerste synchronisatie gebruikt gepagineerde Admin API-calls. De geïnstalleerde, ondertekende companion stuurt gerichte wijzigingen in producten, voorraad, klanten, bestellingen, betalingen en fulfilment. Promoties en voorraadlocaties worden bij de volgende volledige synchronisatie bijgewerkt; die herstelt ook gemiste events.

Klaar om Medusa te verbinden?

Maak je Tender POS-account aan, koppel je Medusa-gegevens en installeer de backendcompanion.

Aan de slag