- 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
Before processing Validation, access control after After processing (in transaction) Data modification, external calls modify 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
b2c-hooks
安装
npx skills add https://github.com/salesforcecommercecloud/b2c-developer-tooling --skill b2c-hooks