| name | b2c-hooks |
| description | Implement hooks using HookMgr and extension points in B2C Commerce. Use when extending OCAPI/SCAPI behavior, handling system events like order calculation, or registering custom hook implementations. Covers hooks.json, dw.ocapi hooks, and custom extension points. |
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
return new Status(Status.OK);
};
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 |
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
return new Status(Status.OK);
};
// 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
Do
- Return
Statusobjects to control flow - Use
request.customto pass data between hooks - Check
request.isSCAPI()when supporting both APIs - Keep hooks focused and performant
- Use custom properties (
c_prefix) in modifyResponse
Don't
- Use transactions in calculate hooks (breaks SCAPI)
- Modify standard response properties (only
c_properties) - Rely on hook execution order across cartridges
- Make 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