Claude-skill-registry b2c-hooks
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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/b2c-hooks" ~/.claude/skills/majiayu000-claude-skill-registry-b2c-hooks && rm -rf "$T"
skills/data/b2c-hooks/SKILL.mdB2C 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 | |
| System | Core business logic | |
| Custom | Your own extension points | |
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 |
|---|---|
| Returns true if hook is registered or has default implementation |
| 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 |
|---|---|---|
| Continues | Hook execution continues |
| 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 processing | Validation, access control |
| After processing (in transaction) | Data modification, external calls |
| 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 |
|---|---|---|
| | Full basket/order calculation |
| | Shipping calculation |
| | 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 |
|---|---|---|
| | Payment authorization |
| | Capture authorized payment |
| | Refund payment |
| | Check authorization validity |
| | Re-authorize expired auth |
Order Hooks
| Extension Point | Function | Purpose |
|---|---|---|
| | 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
objects to control flowStatus - Use
to pass data between hooksrequest.custom - Check
when supporting both APIsrequest.isSCAPI() - Keep hooks focused and performant
- Use custom properties (
prefix) in modifyResponsec_
Don't
- Use transactions in calculate hooks (breaks SCAPI)
- Modify standard response properties (only
properties)c_ - 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