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
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
Each payment provider sends asynchronous status updates through webhook callbacks. Every provider's webhook endpoint follows the same pattern but uses provider-specific verification:
Verification per provider
- Adyen
- Stripe
- Buckaroo
- PayNL
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: 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.
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 (called by the gateway's@requiresSessiondirective)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
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.