home / skills / salesforcecommercecloud / b2c-developer-tooling / b2c-webservices

This skill helps you implement web service integrations in B2C Commerce using LocalServiceRegistry, with configuration, logging, and circuit breakers.

npx playbooks add skill salesforcecommercecloud/b2c-developer-tooling --skill b2c-webservices

Review the files below or copy the command above to add this skill to your agents.

Files (5)
SKILL.md
9.8 KB
---
name: b2c-webservices
description: Implement web service integrations in B2C Commerce using LocalServiceRegistry. Use when calling external APIs, configuring service credentials in services.xml, handling HTTP requests/responses, or implementing circuit breakers. Covers HTTP, SOAP, FTP, and SFTP services.
---

# Web Services Skill

This skill guides you through implementing web service integrations in B2C Commerce using the Service Framework.

## Overview

The Service Framework provides a structured way to call external services with:

| Feature | Description |
|---------|-------------|
| **Configuration** | Service settings managed in Business Manager |
| **Rate Limiting** | Automatic throttling to protect external systems |
| **Circuit Breaker** | Automatic failure handling to prevent cascade failures |
| **Logging** | Communication logging with sensitive data filtering |
| **Mocking** | Test services without external calls |

## Service Types

| Type | Use Case | Protocol |
|------|----------|----------|
| `HTTP` | REST APIs, webhooks | HTTP/HTTPS |
| `HTTPForm` | Form submissions | HTTP/HTTPS with form encoding |
| `FTP` | File transfers (deprecated) | FTP |
| `SFTP` | Secure file transfers | SFTP |
| `SOAP` | SOAP web services | HTTP/HTTPS with SOAP |
| `GENERIC` | Custom protocols | Any |

## Service Framework Components

### Business Manager Configuration

Services are configured in **Administration > Operations > Services**:

1. **Service Configuration** - General settings (enabled, logging, callbacks)
2. **Service Profile** - Rate limiting and circuit breaker settings
3. **Service Credential** - URL and authentication credentials

### Script Components

| Component | Purpose |
|-----------|---------|
| `LocalServiceRegistry` | Creates service instances |
| `ServiceCallback` | Defines request/response handling |
| `Service` | Base service with common methods |
| `Result` | Response object with status and data |

## Basic Pattern

```javascript
'use strict';

var LocalServiceRegistry = require('dw/svc/LocalServiceRegistry');

var myService = LocalServiceRegistry.createService('my.service.id', {
    /**
     * Configure the request before it is sent
     * @param {dw.svc.HTTPService} svc - The service instance
     * @param {Object} params - Parameters passed to service.call()
     * @returns {string} Request body
     */
    createRequest: function (svc, params) {
        svc.setRequestMethod('POST');
        svc.addHeader('Content-Type', 'application/json');
        return JSON.stringify(params);
    },

    /**
     * Parse the response after a successful call
     * @param {dw.svc.HTTPService} svc - The service instance
     * @param {dw.net.HTTPClient} client - The HTTP client with response
     * @returns {Object} Parsed response
     */
    parseResponse: function (svc, client) {
        return JSON.parse(client.text);
    },

    /**
     * Filter sensitive data from logs (required for production)
     * @param {string} msg - The message to filter
     * @returns {string} Filtered message
     */
    filterLogMessage: function (msg) {
        return msg.replace(/("api_key"\s*:\s*")[^"]+"/g, '$1***"');
    }
});

// Call the service
var result = myService.call({ key: 'value' });

if (result.ok) {
    var data = result.object;
} else {
    var error = result.errorMessage;
}
```

## Service Callbacks

| Callback | Required | Description |
|----------|----------|-------------|
| `createRequest` | Yes* | Configure request, return body |
| `parseResponse` | Yes* | Parse response, return result object |
| `execute` | No | Custom execution logic (replaces default) |
| `initServiceClient` | No | Create/configure underlying client |
| `mockCall` | No | Return mock response (execute phase only) |
| `mockFull` | No | Return mock response (entire call) |
| `filterLogMessage` | Recommended | Filter sensitive data from logs |
| `getRequestLogMessage` | No | Custom request log message |
| `getResponseLogMessage` | No | Custom response log message |

*Required unless `execute` is implemented

## Result Object

The `call()` method returns a `dw.svc.Result`:

| Property | Type | Description |
|----------|------|-------------|
| `ok` | Boolean | True if successful |
| `status` | String | "OK", "ERROR", or "SERVICE_UNAVAILABLE" |
| `object` | Object | Response from `parseResponse` |
| `error` | Number | Error code (e.g., HTTP status) |
| `errorMessage` | String | Error description |
| `unavailableReason` | String | Why service is unavailable |
| `mockResult` | Boolean | True if from mock callback |

### Unavailable Reasons

| Reason | Description |
|--------|-------------|
| `TIMEOUT` | Call timed out |
| `RATE_LIMITED` | Rate limit exceeded |
| `CIRCUIT_BROKEN` | Circuit breaker open |
| `DISABLED` | Service disabled |
| `CONFIG_PROBLEM` | Configuration error |

## Error Handling

```javascript
var result = myService.call(params);

if (result.ok) {
    return result.object;
}

// Handle different error types
switch (result.status) {
    case 'SERVICE_UNAVAILABLE':
        switch (result.unavailableReason) {
            case 'RATE_LIMITED':
                // Retry later
                break;
            case 'CIRCUIT_BROKEN':
                // Service is down, use fallback
                break;
            case 'TIMEOUT':
                // Request timed out
                break;
        }
        break;
    case 'ERROR':
        // Check HTTP status code
        if (result.error === 401) {
            // Authentication error
        } else if (result.error === 404) {
            // Resource not found
        }
        break;
}

throw new Error('Service error: ' + result.errorMessage);
```

## Log Filtering

Production environments require log filtering to prevent sensitive data exposure:

```javascript
var myService = LocalServiceRegistry.createService('my.service', {
    createRequest: function (svc, params) {
        // ... configure request
    },

    parseResponse: function (svc, client) {
        return JSON.parse(client.text);
    },

    /**
     * Filter sensitive data from all log messages
     */
    filterLogMessage: function (msg) {
        // Filter API keys
        msg = msg.replace(/api_key=[^&]+/g, 'api_key=***');
        // Filter authorization headers
        msg = msg.replace(/Authorization:\s*[^\r\n]+/gi, 'Authorization: ***');
        // Filter passwords in JSON
        msg = msg.replace(/("password"\s*:\s*")[^"]+"/g, '$1***"');
        return msg;
    },

    /**
     * Custom request log message (optional)
     */
    getRequestLogMessage: function (request) {
        // Return custom message or null for default
        return 'Request: ' + request.substring(0, 100) + '...';
    },

    /**
     * Custom response log message (optional)
     */
    getResponseLogMessage: function (response) {
        // Return custom message or null for default
        return 'Response received';
    }
});
```

## Mocking Services

Use mock callbacks for testing without external calls:

```javascript
var myService = LocalServiceRegistry.createService('my.service', {
    createRequest: function (svc, params) {
        svc.setRequestMethod('GET');
        svc.addParam('id', params.id);
        return null;
    },

    parseResponse: function (svc, client) {
        return JSON.parse(client.text);
    },

    /**
     * Mock the execute phase only (createRequest and parseResponse still run)
     */
    mockCall: function (svc, request) {
        return {
            statusCode: 200,
            text: JSON.stringify({ id: 1, name: 'Mock Data' })
        };
    },

    /**
     * Or mock the entire call (replaces all phases)
     */
    mockFull: function (svc, params) {
        return { id: params.id, name: 'Full Mock Data' };
    }
});

// Force mock mode
myService.setMock();
var result = myService.call({ id: 123 });
```

## Service Configuration in Business Manager

### Creating a Service

1. Go to **Administration > Operations > Services**
2. Click **New** under Service Configurations
3. Fill in:
   - **Service ID**: Unique identifier (e.g., `my.api.service`)
   - **Service Type**: HTTP, FTP, SOAP, etc.
   - **Enabled**: Check to enable
   - **Profile**: Select or create a profile
   - **Credential**: Select or create credentials
   - **Communication Log**: Enable for debugging

### Service Profile Settings

| Setting | Description |
|---------|-------------|
| **Timeout** | Maximum wait time in milliseconds |
| **Rate Limit** | Maximum calls per time unit |
| **Circuit Breaker Enabled** | Enable automatic failure handling |
| **Max Circuit Breaker Calls** | Calls before circuit opens |
| **Circuit Breaker Interval** | Time window for tracking failures |

### Service Credential Settings

| Setting | Description |
|---------|-------------|
| **ID** | Credential identifier |
| **URL** | Base URL for the service |
| **User** | Username for authentication |
| **Password** | Password for authentication |

## Detailed References

- [HTTP Services](references/HTTP-SERVICES.md) - REST API integrations
- [FTP/SFTP Services](references/FTP-SERVICES.md) - File transfer operations
- [SOAP Services](references/SOAP-SERVICES.md) - SOAP web service integrations
- [Services XML](references/SERVICES-XML.md) - Import/export service configurations

## Script API Classes

| Class | Description |
|-------|-------------|
| `dw.svc.LocalServiceRegistry` | Create service instances |
| `dw.svc.Service` | Base service class |
| `dw.svc.HTTPService` | HTTP service methods |
| `dw.svc.FTPService` | FTP/SFTP service methods |
| `dw.svc.SOAPService` | SOAP service methods |
| `dw.svc.Result` | Service call result |
| `dw.svc.ServiceConfig` | Service configuration |
| `dw.svc.ServiceProfile` | Rate limit/circuit breaker config |
| `dw.svc.ServiceCredential` | Authentication credentials |
| `dw.net.HTTPClient` | Underlying HTTP client |
| `dw.net.FTPClient` | Underlying FTP client |
| `dw.net.SFTPClient` | Underlying SFTP client |

Overview

This skill explains how to implement web service integrations in B2C Commerce using the LocalServiceRegistry and the platform Service Framework. It covers configuring services in Business Manager, writing service callbacks, handling HTTP/SOAP/FTP/SFTP calls, and using rate limiting, circuit breakers, logging, and mocking. The guidance focuses on reliable request/response handling, credential management, and safe logging for production.

How this skill works

You create service instances with LocalServiceRegistry.createService and implement callbacks such as createRequest and parseResponse (or execute) to shape requests and parse responses. Services are wired to Business Manager configurations (service config, profile, credential) which control timeouts, rate limits, and circuit breaker behavior. The call() method returns a Result object indicating success, errors, or unavailable reasons; mock callbacks let you test without external calls.

When to use it

  • Calling external REST or SOAP APIs from B2C Commerce controllers or jobs
  • Transferring files via SFTP/FTP from the platform
  • Protecting integrations with rate limiting and circuit breaker policies
  • Testing integrations locally or in CI using mockCall/mockFull
  • Centralizing credentials and endpoints in Business Manager for ops control

Best practices

  • Always store endpoints and credentials in Business Manager service credentials, not in code
  • Implement filterLogMessage to sanitize API keys, passwords, and authorization headers
  • Prefer parseResponse/createRequest pair unless you need full custom execution via execute
  • Use service profiles to set timeouts, rate limits, and circuit breaker thresholds appropriate for the external system
  • Use mockCall or mockFull in unit tests and CI to avoid hitting production APIs

Example use cases

  • Querying a product enrichment REST API from a controller for enhanced product data
  • Calling a payment gateway SOAP service during order placement with retry and error handling
  • Uploading daily inventory files to a vendor via SFTP in a scheduled job
  • Implementing a webhook receiver that forwards events to an external HTTP service with rate limiting
  • Creating a mock service in CI to validate business logic without external dependencies

FAQ

What callbacks are required for a service?

Either implement createRequest and parseResponse, or implement execute to take full control of the call lifecycle.

How do I prevent sensitive data appearing in logs?

Implement filterLogMessage to remove API keys, passwords, and authorization headers before messages are logged.