How to use OCAPI/SCAPI hooks

OCAPI and SCAPI Hooks

As the Composable Storefront (PWA Kit) and SCAPI/OCAPI are being used increasingly, the need to customise these endpoints becomes a basic necessity. How can we implement these customisations? Where do we start?

At the end of this article, all of these questions should be answered, and you will be well on your way to customising these endpoints and possibly building your own!

What are “hooks”?

“Hooks” is a system that has been put in place to allow (limited) customisation of the standard REST APIs that Salesforce B2C Commerce Cloud provides.

By hooking into the “before,” “after,” or “modify” functions, you can customise the functionality behind these endpoints.

Typical use cases are:

  • before-Hooks: define additional filter criteria for the API execution, and perform certain status checks before a PATCH request.

  • after-Hooks: add information after the service logic to indicate how to finalise the step (such as setting the status for a payment method) and integrate with third-party systems (such as order placements).

  • modifyResponse-Hooks: extend result sets with additional information to an object (for example, additional product detail information) and cleanup result sets of unwanted properties.

Not all APIs are made equal

Before starting this journey together, the most important thing to understand is that not all endpoints support hooks. An overview for both types is available:

Feature switch for SCAPI

As it stands, Salesforce B2C Commerce Cloud disables hooks by default for the SCAPI. To enable hook support, a feature switch needs to be enabled in the Business Manager:

“Administration > Global Preferences > Feature Switches”

Step 1: Register your customizations

The first step in writing hooks for our APIs is registering them with the server. To do this, you need to do the following steps.

Create a “hooks.json” file

We need to create a JSON file that describes which endpoints we want to customise called “hooks.json.” This file can be put anywhere in a cartridge. But in this case, we will put it in the root  ( e.g. “my_project/cartridges/my_cartridge/hooks.json ) as an example.

				
					{
    "hooks": [
        {
            "name": "dw.ocapi.shop.basket.beforePATCH",
            "script": "./cartridge/scripts/hooks/basketHooks.js"
        },
        {
            "name": "dw.ocapi.shop.customers.password_reset.afterPOST",
            "script": "./cartridge/scripts/hooks/passwordHooks.js"
        }
    ]
}
				
			

In this file, we “hook” the customisation script to the REST endpoint we want to extend.

We can define as many as we want within the file! But make sure every “name” is unique. If it is not, there might be some unexpected behaviour.

Update the “package.json” cartridge file

The next step is to create or edit your cartridge’s “package.json” file.

The file should be in the root folder of your cartridge. (e.g. “my_project/cartridges/my_cartridge/package.json”)

				
					{
  "hooks": "./hooks.json",
  ...
}
				
			

Salesforce B2C Commerce Cloud parses this file to enable specific customisations, including hooks.

Step 2: Build your customisations

You probably noticed that we need to define a “script” file for each hook we register. But we have not created those files until now, so let us do that!

Look up the endpoint documentation

Before we start writing the code, we need to know what function to export in our script for the system to pick up our customisation. This information can be found in the Infocenter.

First, we locate the endpoint we want to override. The documentation will show us more information about the function behind it.

In this case, we need to export the function “beforePATCH” with the parameters “basket” and “basketInput.”

Put that documentation to work

Now that we know what function to use, we can start writing some code.

				
					/**
 * This can be used to update the basket server side, if for instance we need to call a tax service or sync the basket.
 * The client app can retrieve this updated basket by doing a PATCH request.
 */
exports.beforePATCH = function (basket, basketInput) {
    var productLineItems = basket.getProductLineItems();

    /** pass on something to ensure hooks are executed */
    for (var i = 0; i < productLineItems.length; i += 1) {
        productLineItems[i].setLineItemText('PRODUCT ' + productLineItems[i].getLineItemText());
    }
};
				
			

Detecting OCAPI vs SCAPI

We may have a scenario where the OCAPI and the SCAPI use the same endpoint and have their unique customisations. To detect SCAPI calls, the request object/class has recently received a helper function:

				
					request.isSCAPI()
				
			

Step 3: Test if it works!

That wasn’t so much work now, was it? All that is left is to test that our custom code is executed correctly! I recommend Postman to do so.

Maybe a list of things to keep in mind:

  • Don’t forget to upload your cartridge!
  • Don’t forget to add the cartridge to the correct cartridge path!
  • Call the correct endpoint!
  • Call the correct environment!


Some of these might seem obvious, but it is easy to get mixed up when working with tools such as Postman.

Catch and monitor exceptions

We must build our customisations to be robust, as an exception can cause the entire API to fail!

Be sure to catch exceptions and log them appropriately so we can monitor and fix any exceptions that might occur.

Constraints and best practices

Salesforce has provided a list of constraints and best practices on the documentation site!

Implement the same hook in multiple cartridges

In a single hooks.json file, you can register multiple modules to call for an extension point. However, you can't control the order in which the modules are called. If you call multiple modules, only the last hook returns a value. All modules are called, regardless of whether any of them return a value.

At run time, B2C Commerce runs all hooks registered for an extension point in all cartridges in your cartridge path. Hooks are executed in the order their cartridges appear on the path. Each cartridge can register a module for the same hook. Modules are called in cartridge-path order for all cartridges in which they are registered.

The text above has been taken from the Salesforce B2C Commerce Cloud Infocenter and turns out not to be correct (at least for SCAPI/OCAPI hooks.

This does have a slight nuance: It is not the case for all endpoints. Luckily this is documented for every hook!

Read the documentation carefully for each hook!

So basically: never return the following code in your hooks when your custom code completes successfully (if the endpoint supports it):

				
					return new Status(Status.OK);
				
			

Sometimes, your linter will complain about not returning a value in all branches. But you must ignore that warning to avoid breaking another cartridge hook. (Unless you want to break the chain!)

An example where a linter will complain:

				
					exports.beforePOST = function beforePOST(registration) {
    var Status = require('dw/system/Status');

    var verificationResult = validate(registration.customer);

    if (!verificationResult) {
        return new Status(Status.ERROR, 'ERR-TS-02', Resource.msg('turnstile.errors.ERR-TS-02', 'turnstile', null));
    }
    
    // Your linter will want a return statement here
};

				
			

What about creating new endpoints?

For now, this is not possible out of the box. But there is some good news; we might be getting that feature in 2023! But, as with all things on the roadmap – Forward-Looking Statement!

There is a workaround for now that I wrote an extensive article about, allowing you to create custom OCAPI endpoints.

Controllers

As a last resort, we can write a custom endpoint using Controllers. If this route is chosen, there are a few things to keep in mind:

  • The session bridge is required for personalisation and secure customer recognition

  • The default workings are active, so the system will start setting cookies which are usually not part of a REST API setup.

  • There is code overhead in the SFRA server module, and I have built a custom HRA with all of the overhead removed.

    !!! Keep in mind that this is a work in progress. !!!

  •  Not all HTTP Methods are supported by default in SFRA (also fixed in the HRA mentioned above)

Table of Contents

Facebook
Twitter
LinkedIn