Customization with Hooks

Hooks allow you to alter and extend the behaviour of existing Shopper API resources using the Script API.

Only existing customers can access some of the links on this page. Visit Salesforce Commerce Cloud GitHub Repositories and Access for information about how to get access to the Commerce Cloud repositories.

For more information on Script API, refer to the following documentation:

Resources can provide the following hooks:

  • before: Useful for custom validation, for example, if you're sending an address that you want to validate.
  • after: Useful for changing data, for example: you're sending payment information that you need to verify with an external system and then persist it.
  • modifyResponse: Allow you to modify what is contained in the response, for example: If there’s an attribute that you don't need or an additional attribute that you do need, you can use a response hook to modify what is obtained from an external system.

Available Hooks

GET requests support before and modifyResponse hooks. State changing HTTP methods like POST, PATCH, PUT, and DELETE additionally support after hooks. The latter also writes transactions to the database accordingly.

Hooks don't apply to all Shopper APIs. For example, the Shopper Experience API provides its own customization capabilities for page and component types.

The Hook List contains available hooks.

To use hooks for Commerce API, first enable it in Business Manager for your B2C Commerce instance.

Navigate to Administration > Global Preferences > Feature Switches and check Enable Salesforce Commerce Cloud API hook execution.

To enable, you must have the Account Manager role of Business Manager Administrator for the instance.

  1. Place a package.json file in the top-level directory of your cartridge.
  2. In the package.json file, define the hooks property to the path of the hooks.json configuration file. This path is relative to the directory containing the package.json file:
  3. In the hooks.json file, configure an array with the mappings of hooks to their script files with paths relative to the hooks.json file:
  4. For site specific use, register the cartridge to each appropriate site in Business Manager. To customize organization-level resources across all sites, such as libraries, register the cartridge to the Business Manager site.

Hook scripts are loaded as CommonJS modules. Hook functions must be exported. The exported name must match the name of the hook, without the package qualification. For example, the dw.ocapi.shop.basket.billing_address.beforePUT hook is exported as beforePUT:

For each hook, your code SHOULD return a Status object to the server. If the status is OK, the server continues processing. If the status is ERROR, representing a handled exception, the server stops further processing, rolls back the transaction and responds with an HTTP 400 Bad Request fault. When an ERROR occurs, the server returns a fault to the caller, containing information like the error code, message, and details from the Status object. Uncaught exceptions in your code including the errors you throw cause an HTTP 500 Internal Error fault; in this case, the server rolls back the transaction.

When an ERROR occurs, the server returns an RFC IETF rfc7807 ErrorResponse to the caller, containing information like the error code, message, and details from the Status object.

If your hook code does not return a Status object, multiple registered hook scripts might be executed, including the overridden base implementation. The Status object or value returned by these hooks will then be returned instead.

A single request can call multiple hooks. For example: adding a new payment instrument to a basket calls:

  • dw.ocapi.shop.basket.payment_instrument.beforePOST
  • dw.ocapi.shop.basket.payment_instrument.afterPOST
  • dw.ocapi.shop.basket.payment_instrument.modifyPOSTResponse

To pass data between hooks, use request.custom. JavaScript objects added to the request are available in subsequent hooks.

For example, to call a 3rd party payment processor inside the transaction of adding a new payment instrument to the basket, you can make this call within the dw.ocapi.shop.basket.payment_instrument.afterPOST hook (to ensure any errors rollback the transaction). After calling the payment service provider to initialize the payment request, you can return the relevant data to the client in the dw.ocapi.shop.basket.payment_instrument.modifyPOSTResponse hook. To do this, add relevant data to the request.custom container to be handled by the modifyPOSTResponse hook.

As a result of many projects with customers, our community is maintaining a hook collection with many useful implementations to extend functionality or even provide new functionality. The hooks in the community collection cover a wide range of practical use cases.

For more information, check out the OCAPI Hooks Collection on the Salesforce Commerce Cloud GitHub repository.

The Calculate hook, dw.order.calculate, enables you to implement customized basket calculation logic. It can be used as the single place for basket calculation and recalculation. This hook provides a default implementation, can be overridden. The default logic of the following hooks implicitly call this hook:

  • dw.ocapi.baskets.actions.afterMerge
  • dw.ocapi.baskets.actions.afterTransfer
  • dw.ocapi.shop.basket.afterPATCH
  • dw.ocapi.shop.basket.afterPOST
  • dw.ocapi.shop.basket.agent.afterPUT
  • dw.ocapi.shop.basket.billing_address.afterPUT
  • dw.ocapi.shop.basket.coupon.afterDELETE
  • dw.ocapi.shop.basket.coupon.afterPOST
  • dw.ocapi.shop.basket.customer.afterPUT
  • dw.ocapi.shop.basket.gift_certificate_item.afterDELETE
  • dw.ocapi.shop.basket.gift_certificate_item.afterPATCH
  • dw.ocapi.shop.basket.gift_certificate_item.afterPOST
  • dw.ocapi.shop.basket.item.afterDELETE
  • dw.ocapi.shop.basket.item.afterPATCH
  • dw.ocapi.shop.basket.items.afterPOST
  • dw.ocapi.shop.basket.payment_instrument.afterDELETE
  • dw.ocapi.shop.basket.payment_instrument.afterPATCH
  • dw.ocapi.shop.basket.payment_instrument.afterPOST
  • dw.ocapi.shop.basket.price_adjustment.afterDELETE
  • dw.ocapi.shop.basket.price_adjustment.afterPATCH
  • dw.ocapi.shop.basket.price_adjustment.afterPOST
  • dw.ocapi.shop.basket.reference.afterPOST
  • dw.ocapi.shop.basket.shipment.afterDELETE
  • dw.ocapi.shop.basket.shipment.afterPATCH
  • dw.ocapi.shop.basket.shipment.afterPOST
  • dw.ocapi.shop.basket.shipment.shipping_address.afterPUT
  • dw.ocapi.shop.basket.shipment.shipping_method.afterPUT
  • dw.ocapi.shop.basket.storefront.afterPUT
  • dw.ocapi.shop.order.beforePOST
  • dw.ocapi.shop.order.beforePUT

The following code snippet shows a sample call:

In this sample call, the parameters are:

  • "dw.order.calculate" - the extension point to call
  • "calculate" - the script function to call
  • basket - the basket to be calculated

SiteGenesis uses the default implementation of the dw.order.calculate hook for basket calculation logic.

Using hooks with the Salesforce Commerce API (SCAPI) is similar to using hooks with OCAPI, but there are differences developers must be aware of. When you enable and maintain hooks, the same hooks are called for both SCAPI and the related OCAPI endpoints, so it’s possible to write a hook that is used for both. This is important if you use both API frameworks for your client applications.

Use request.isSCAPI() to determine SCAPI or OCAPI usage, especially if you’re already using the calculate hook in the context of controllers and use transactions in that hook, as that breaks SCAPI.

To add conditional behavior to a hook, use custom query string parameters. Parameters must be prefixed with c_:

SCAPI supports custom HTTP request and response headers for diagnostic and informational purposes.

  • Custom headers must be prefixed with c_.
  • Do not use custom headers to implement conditional behaviour, because custom headers are not considered when constructing a cache key. For details, see Server-Side Web-Tier Caching.
  • Never use custom request headers in a way that affects the response body. Custom headers are only intended for diagnostic or informational purposes, such as logging.

The following example logs an error message for a required parameter value missing in the request header:

The following example provides a sample custom response header:

You can use SCAPI and OCAPI Hooks to implement custom validation logic to parse and handle errors.

For example, to use a hook to add custom validation of shipping addresses:

Use Status.addDetail(key, value) to add details to the error that the client can parse and use. For additional details, see Class Status.

If isValidAddress is false, SCAPI returns a HTTP 400 response with the content type: application/problem+json:

If your hook raises an unhandled exception or error (network error, code syntax error), then an HTTP 500 response is returned:

The Hook Circuit Breaker protects the system from excessive hook execution failures. If your hook generates too many errors, the circuit breaker is activated and returns an HTTP 503 response:

For more information, see Hook Circuit Breaker.

If a hook errors during processing, the request will fail. You can track hook errors with Log Center. Configure a search using the LCQL query category: ( com.demandware.wapi.servlet.ShopRestServlet ) AND stackTrace: ( HookInvocationException ).

All Shopper APIs have a timeout of 10 seconds except Shopper Products which has a timeout of 30 seconds. Responses that take longer than this time return an error.

modifyResponse hooks are a powerful way to create a response that best fits the needs of the application. You can add additional information to objects, or provide a more UI-friendly property set to improve the consumption on the client application.

Additional properties must be added only as custom properties (c_ properties).

When you enable caching for endpoints that use SCAPI hooks, the hook logic is executed by the application server and the result is cached. Neither the application server logic nor custom hook logic is executed again until the object expires from the cache. For details, see Server-Side Web-Tier Caching.

Basket calculations built into the B2C Commerce backend are enabled for determining basket information. SCAPI endpoints allow the manipulation of tax information on the basket.

With B2C Commerce 24.5, the following endpoints are also supported with hooks enabled:

  • /checkout/baskets/{}/taxes
  • /checkout/orders/{}/taxes
  • /checkout/baskets/{}/items/{}/taxes

The following endpoint is not supported with hooks enabled. Calling this endpoint with hooks enabled results in a HTTP 409 error response from the request.

  • /checkout/baskets/{basketId}/price-books