REST endpoints & webhooks
Evolve services primarily expose a GraphQL subgraph for client consumption. REST endpoints are used for integrations that don’t fit the GraphQL model: webhook callbacks from external providers, session management, commercetools API extensions, and payment flows.
When REST, when GraphQL
Section titled “When REST, when GraphQL”GraphQL is the API for frontend clients and service-to-service queries through federation. All product, cart, account, and order operations go through GraphQL.
REST is used when:
- An external system pushes data in (payment webhooks, CMS webhooks)
- The protocol requires it (commercetools API extensions, OAuth token endpoints)
- The interaction is a simple request/response without the flexibility needs of GraphQL (session creation, health checks)
Payment webhooks
Section titled “Payment webhooks”Each payment provider sends asynchronous status updates through webhook callbacks. Every provider’s webhook endpoint follows the same pattern but uses provider-specific verification:
sequenceDiagram
participant PSP as Payment provider
participant WH as Webhook endpoint
participant CT as commercetools
participant Bus as Event bus
PSP->>WH: POST /push (status update)
WH->>WH: Verify signature
WH->>CT: Update transaction status
CT-->>WH: Confirmation
CT->>Bus: PaymentStatusStateTransition event
WH-->>PSP: 200 OK / 202 Accepted
Verification per provider
Section titled “Verification per provider”Method: HMAC
Validates each notificationItem with hmacValidator.validateHMAC() using ADYEN_HMAC_KEY. Returns 202 Accepted on success, 401 on invalid HMAC.
Adyen decouples webhook acknowledgement from processing. The
webhook handler publishes each notification to an internal queue and
returns 202 immediately. A separate consumer processes notifications
and updates commercetools. This prevents Adyen from timing out and
retrying when commercetools mutations are slow.
Method: Signature
Uses stripe.webhooks.constructEvent(rawBody, sig, STRIPE_WEBHOOK_SECRET) with the stripe-signature header. Requires raw body access (Fastify rawBody plugin).
Method: HMAC
Uses the Buckaroo SDK’s ReplyHandler to validate the Authorization header against the canonical URL. Reconstructs the URL from SERVER_URL config since the service runs behind a reverse proxy.
Method: Fetch-and-verify
Mollie webhooks carry only a payment ID, so there is no signature to
check. The handler looks up the commercetools Payment by
interfaceId, then re-fetches the payment status from Mollie’s API
rather than trusting the webhook payload. The endpoint returns TRUE
(plain text) on success.
Method: HMAC
Custom preHandler reads signature-method, signature-algorithm, and signature headers. Computes crypto.createHmac(algorithm, secret).update(rawBody).digest("hex") and compares.
PayNL re-fetches the payment status from PayNL’s API after webhook
verification rather than trusting the webhook payload directly. This
prevents a stale retry from overwriting a more recent status. The
endpoint returns TRUE (plain text) on success.
The Invoice provider (pay on account) is fully offline: there is no PSP to call back, so it exposes no webhook endpoint.
Session endpoints
Section titled “Session endpoints”The account service exposes REST endpoints for session management. These are documented in detail in Authentication & tokens:
POST /api/create-session: creates an anonymous session (REST alternative to thecreateGuestSessionmutation)POST /token/anonymous: creates an anonymous session with raw token info (used by server-side consumers)POST /api/client-context: returns customer metadata for a given access token (cached in Redis)
commercetools API extensions
Section titled “commercetools API extensions”commercetools API extensions are synchronous HTTP callbacks that fire when resources are created or updated. They allow services to modify resources during the API call, for example setting defaults on a new cart or generating an order number.
The checkout service registers extensions for:
| Resource | Action | Behavior |
|---|---|---|
| Cart | Create | Copies customer addresses and email onto the cart. For B2B, uses the business unit’s addresses. |
| Cart | Update | Auto-selects a default shipping method when a shipping address is set. |
| Order | Create | Generates a unique order number, ensures customer email is set, applies default order type. |
| Payment | Create | Transitions the payment to the PaymentInitial state. |
Extensions are registered through Terraform with a shared secret for authentication:
resource "commercetools_api_extension" "main" { destination { type = "HTTP" url = "${api_gateway_hostname}/${service_name}/extension" authorization_header = "Bearer ${random_password.api_extension_secret.result}" }
trigger { resource_type_id = "cart" actions = ["Create", "Update"] }
trigger { resource_type_id = "order" actions = ["Create"] }
trigger { resource_type_id = "payment" actions = ["Create"] }}The service validates the Authorization: Bearer <secret> header on
every extension request. The handler dispatches based on the resource
type and action, then returns update actions or errors:
const handlers = { cart: { Create: createCartExtension, Update: updateCartExtension, }, order: { Create: createOrderExtension, }, payment: { Create: (payment) => ({ actions: [{ action: "transitionState", state: { typeId: "state", key: "PaymentInitial" } }], }), },};Because API extensions are synchronous, they add latency to the commercetools API call. Keep extension logic fast and avoid external API calls where possible.

