b2c-hooks

Hooks are extension points that allow you to customize business logic by registering scripts. B2C Commerce supports two types of hooks:

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "b2c-hooks" with this command: npx skills add salesforcecommercecloud/b2c-developer-tooling/salesforcecommercecloud-b2c-developer-tooling-b2c-hooks

B2C Commerce Hooks

Hooks are extension points that allow you to customize business logic by registering scripts. B2C Commerce supports two types of hooks:

  • OCAPI/SCAPI Hooks - Extend API resources with before, after, and modifyResponse hooks

  • System Hooks - Custom extension points for order calculation, payment, and other core functionality

Hook Types Overview

Type Purpose Examples

OCAPI/SCAPI Extend API behavior dw.ocapi.shop.basket.afterPOST

System Core business logic dw.order.calculate

Custom Your own extension points app.checkout.validate

Hook Registration

File Structure

my_cartridge/ ├── package.json # References hooks.json └── cartridge/ └── scripts/ ├── hooks.json # Hook registrations └── hooks/ # Hook implementations ├── basket.js └── order.js

package.json

Reference the hooks configuration file:

{ "name": "my_cartridge", "hooks": "./cartridge/scripts/hooks.json" }

hooks.json

Register hooks with their implementing scripts:

{ "hooks": [ { "name": "dw.ocapi.shop.basket.afterPOST", "script": "./hooks/basket.js" }, { "name": "dw.ocapi.shop.basket.modifyPOSTResponse", "script": "./hooks/basket.js" }, { "name": "dw.order.calculate", "script": "./hooks/order.js" } ] }

Hook Script

Export functions matching the hook method name (without package prefix):

// hooks/basket.js var Status = require('dw/system/Status');

exports.afterPOST = function(basket) { // Called after basket creation // Returning a value would skip system implementation };

exports.modifyPOSTResponse = function(basket, basketResponse) { // Modify the API response basketResponse.c_customField = 'value'; };

HookMgr API

Use dw.system.HookMgr to call hooks programmatically:

var HookMgr = require('dw/system/HookMgr');

// Check if hook exists if (HookMgr.hasHook('dw.order.calculate')) { // Call the hook var result = HookMgr.callHook('dw.order.calculate', 'calculate', basket); }

Method Description

hasHook(extensionPoint)

Returns true if hook is registered or has default implementation

callHook(extensionPoint, functionName, args...)

Calls the hook, returns result or undefined

Status Object

Hooks return dw.system.Status to indicate success or failure:

var Status = require('dw/system/Status');

// Success - continue processing return new Status(Status.OK);

// Error - stop processing, rollback transaction var status = new Status(Status.ERROR); status.addDetail('error_code', 'INVALID_ADDRESS'); status.addDetail('message', 'Address validation failed'); return status;

Status HTTP Response Behavior

Status.OK

Continues Hook execution continues

Status.ERROR

400 Bad Request Transaction rolled back, processing stops

Uncaught exception 500 Internal Error Transaction rolled back

Return Value Behavior (Important)

OCAPI/SCAPI hooks that return ANY value will SKIP the system implementation and all subsequent registered hooks for that extension point.

This is a common source of bugs. For example, if a hook returns Status.OK , the system's dw.order.calculate implementation won't run, causing cart totals to be incorrect.

When to Return a Value

Return a Status object only when you want to:

  • Stop processing with an error (Status.ERROR )

  • Skip the system implementation intentionally

When NOT to Return a Value

To ensure system implementations run (like cart calculation), return nothing:

// Returning Status.OK skips system implementation exports.afterPOST = function(basket) { doSomething(basket); return new Status(Status.OK); // Skips dw.order.calculate };

// No return value - system implementation runs exports.afterPOST = function(basket) { doSomething(basket); // No return, or explicit: return; };

Summary

Return Value OCAPI/SCAPI Behavior Custom Hook Behavior

undefined (no return) System implementation runs, subsequent hooks run All hooks run

Status.OK

Skips system implementation and subsequent hooks All hooks run

Status.ERROR

Stops processing, returns error All hooks run

Debugging tip: If cart totals are wrong or hooks aren't firing, check if an earlier hook is returning a value.

OCAPI/SCAPI Hooks

OCAPI and SCAPI share the same hooks. Enable in Business Manager: Administration > Global Preferences > Feature Switches > Enable Salesforce Commerce Cloud API hook execution

Hook Types

Hook When Called Use Case

before<METHOD>

Before processing Validation, access control

after<METHOD>

After processing (in transaction) Data modification, external calls

modify<METHOD>Response

Before response sent Add/modify response properties

Common Hook Patterns

// Validation in beforePUT exports.beforePUT = function(basket, addressDoc) { if (!isValidAddress(addressDoc)) { var status = new Status(Status.ERROR); status.addDetail('validation_error', 'Invalid address'); return status; } };

// External call in afterPOST (within transaction) exports.afterPOST = function(basket, paymentDoc) { var result = callPaymentService(paymentDoc); request.custom.paymentResult = result; // Pass to modifyResponse // Returning a Status would skip system implementation };

// Modify response exports.modifyPOSTResponse = function(basket, basketResponse, paymentDoc) { basketResponse.c_paymentStatus = request.custom.paymentResult.status; };

Passing Data Between Hooks

Use request.custom to pass data between hooks in the same request:

// In afterPOST exports.afterPOST = function(basket, doc) { request.custom.externalId = callExternalService(); };

// In modifyPOSTResponse exports.modifyPOSTResponse = function(basket, response, doc) { response.c_externalId = request.custom.externalId; };

Detect SCAPI vs OCAPI

exports.afterPOST = function(basket) { if (request.isSCAPI()) { // SCAPI-specific logic } else { // OCAPI-specific logic } };

System Hooks

Calculate Hooks

Extension Point Function Purpose

dw.order.calculate

calculate

Full basket/order calculation

dw.order.calculateShipping

calculateShipping

Shipping calculation

dw.order.calculateTax

calculateTax

Tax calculation

// hooks/calculate.js var Status = require('dw/system/Status'); var HookMgr = require('dw/system/HookMgr');

exports.calculate = function(lineItemCtnr) { // Calculate shipping HookMgr.callHook('dw.order.calculateShipping', 'calculateShipping', lineItemCtnr);

// Calculate promotions, totals...

// Calculate tax
HookMgr.callHook('dw.order.calculateTax', 'calculateTax', lineItemCtnr);

return new Status(Status.OK);

};

Payment Hooks

Extension Point Function Purpose

dw.order.payment.authorize

authorize

Payment authorization

dw.order.payment.capture

capture

Capture authorized payment

dw.order.payment.refund

refund

Refund payment

dw.order.payment.validateAuthorization

validateAuthorization

Check authorization validity

dw.order.payment.reauthorize

reauthorize

Re-authorize expired auth

Order Hooks

Extension Point Function Purpose

dw.order.createOrderNo

createOrderNo

Custom order number generation

var OrderMgr = require('dw/order/OrderMgr'); var Site = require('dw/system/Site');

exports.createOrderNo = function() { var seqNo = OrderMgr.createOrderSequenceNo(); var prefix = Site.current.ID; return prefix + '-' + seqNo; };

Custom Hooks

Create your own extension points:

// Define custom hook var HookMgr = require('dw/system/HookMgr');

function processCheckout(basket) { // Call custom hook if registered if (HookMgr.hasHook('app.checkout.validate')) { var status = HookMgr.callHook('app.checkout.validate', 'validate', basket); if (status && status.error) { return status; } } // Continue processing... }

Register in hooks.json:

{ "hooks": [ { "name": "app.checkout.validate", "script": "./hooks/checkout.js" } ] }

Custom hooks always execute all registered implementations regardless of return value.

Remote Includes in Hooks

Enhance API responses with data from other SCAPI endpoints:

var RESTResponseMgr = require('dw/system/RESTResponseMgr');

exports.modifyGETResponse = function(product, doc) { // Include Custom API response var include = RESTResponseMgr.createScapiRemoteInclude( 'custom', // API family 'my-api', // API name 'v1', // Version 'endpoint' // Endpoint ); doc.c_additionalData = { value: [include] }; };

Best Practices

  • Return undefined (no return) from OCAPI/SCAPI hooks to ensure system implementations run

  • Only return Status.ERROR when you need to stop processing

  • Returning Status.OK skips system implementation and subsequent hooks

  • Use request.custom to pass data between hooks

  • Check request.isSCAPI() when supporting both APIs

  • Keep hooks focused and performant

  • Use custom properties (c_ prefix) in modifyResponse

  • Avoid transactions in calculate hooks (breaks SCAPI)

  • Avoid slow external calls in beforeGET (affects caching)

Error Handling

Circuit Breaker

Too many hook errors triggers circuit breaker (HTTP 503):

{ "title": "Hook Circuit Breaker", "type": "https://api.commercecloud.salesforce.com/.../hook-circuit-breaker", "detail": "Failure rate above threshold of '50%'", "extensionPointName": "dw.ocapi.shop.basket.afterPOST" }

Timeout

Hooks must complete within the SCAPI timeout (HTTP 504 on timeout).

Detailed References

  • OCAPI/SCAPI Hooks - API hook patterns and available hooks

  • System Hooks - Calculate, payment, and order hooks

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.