This guide covers integrating HubBox pickup point functionality into custom-built ecommerce websites, headless commerce setups, or heavily customised platform checkouts using the HubBox Software Development Kit (SDK).
The SDK offers the highest degree of flexibility and control over the user experience and integration logic, allowing you to embed HubBox features within your unique checkout flow. This approach requires frontend development to interact with HubBox components and backend development to handle data persistence, filtering logic, and order processing.
SDK vs. Tag: If you need a quicker, lower-code frontend solution with less customisation required, consider the HubBox Tag solution. The SDK provides maximum control but involves more development effort.
configId accessed via "Credential Management" section within Console.You need to be logged in to Console to access the above documentation.
The SDK integration involves several key pieces working together:
Launch Experience UI - You design and build the UI elements within your checkout that allow the customer to choose between Home Delivery and Local Pickup (e.g., buttons, toggles, radio options, or a shipping method choice). Alternatively, you can implement this UI using HubBox's pre-built web components for a toggle launch experience.
HubBox Map Widget (SingleWidgetManager) - The core piece, managed by the @hubbox/single-widget-manager JavaScript library, loads and displays the interactive map and list of pickup locations within an iframe. It can be presented either embedded directly on the page or within a modal pop-up.
Event-Driven Communication - Your frontend JavaScript interacts with the SingleWidgetManager using a publish/subscribe event system (events.subscribe, events.emit) to trigger searches, open the widget, and receive data about the user's selection.
Configuration - Key settings like your configId (defining networks, styling), locale, and environment (sandbox/production) are passed during initialisation of the SingleWidgetManager.
Backend Interaction (Retailer Built) - Your frontend JavaScript communicates the selected pickup point data (address, ID, network) to your backend server (e.g., via AJAX/Fetch). Your backend logic must then:
Order Data Sharing - Sending details of completed pickup orders back to HubBox via the Order Processing API or Tracking Pixel allows you to leverage HubBox data dashboards.
Install the necessary HubBox packages into your frontend project using NPM or Yarn:
npm install @hubbox/single-widget-manager @hubbox/web-components
# or
yarn add @hubbox/single-widget-manager @hubbox/web-components
If you don't use NPM or Yarn, contact HubBox Integrations for CDN URLs or minified versions.
If using TypeScript or certain module bundlers, import the packages as follows:
import SingleWidgetManager from '@hubbox/single-widget-manager';
import "@hubbox/web-components"; // Imports and registers custom elements like <hubbox-modal>
Before initialising the widget, decide how customers will opt into pickup and build the corresponding UI.
Choose a pattern:
You can build this UI using standard HTML elements (buttons, radio inputs, search fields) with appropriate event listeners, or use the pre-built /docs/hubbox-components/components-installation such as <hubbox-local-pickup-toggles>, <hubbox-home-delivery-toggles>, <hubbox-local-pickup-multi-button>, and <hubbox-home-delivery-multi-button>.
This approach presents HubBox pickup options directly alongside your standard home delivery rates within the shipping method selection step of your checkout.

User flow:
Key considerations:
SingleWidgetManager (often via iframeParams during initialisation) so it displays locations for the correct carrier. HubBox can set up multiple configIds with different networks configured.This pattern presents customers with a clear, high-level choice between "Home Delivery" and "Pickup Points" early in the shipping step, often before they interact with the main shipping address form fields.

User flow:
Key considerations:
Instantiate the manager in your frontend JavaScript. Choose one of the following modes:
Modal Mode (Recommended) Embedded ModeThe Widget appears in a pop-up modal. This is the recommended approach for most checkout implementations.
import SingleWidgetManager from '@hubbox/single-widget-manager';
const singleWidgetManager = new SingleWidgetManager({
deferRender: true, // IMPORTANT: Prevents the iframe from rendering immediately
iframeUrl: SingleWidgetManager.iframeUrls.PRODUCTION, // Use .SANDBOX for testing
// healthCheck: false, // Optional: Disable polling check if causing issues
// isDebug: true, // Optional: Enable verbose logging during development
iframeParams: {
configId: "YOUR_HUBBOX_CONFIG_ID", // Provided by HubBox
locale: "en-GB" // Match your store's locale (e.g., en-US, fr-FR)
// Add other config params like 'translations' if needed
}
});
You'll also need to create and manage the <hubbox-modal> component (see Step 4) or use your own modal UI.
The Widget appears directly within a container element on your page.
import SingleWidgetManager from '@hubbox/single-widget-manager';
// Ensure this div exists in your HTML: <div id="hubbox-widget-container"></div>
const widgetContainer = document.getElementById("hubbox-widget-container");
const singleWidgetManager = new SingleWidgetManager({
container: widgetContainer, // REQUIRED: The DOM element to inject the iframe into
iframeUrl: SingleWidgetManager.iframeUrls.PRODUCTION, // Use .SANDBOX for testing
// isDebug: true,
iframeParams: {
configId: "YOUR_HUBBOX_CONFIG_ID",
locale: "en-GB"
}
});
Connect your Launch Experience UI (from Step 2) to the SingleWidgetManager.

1. Add the modal component to your page (on load or dynamically):
<hubbox-modal></hubbox-modal>
Or create it dynamically via JavaScript:
const hubboxModal = document.createElement("hubbox-modal");
document.body.appendChild(hubboxModal); // Append somewhere appropriate
2. Configure the modal and link the iframe (usually after the manager initialises):
const hubboxModal = document.querySelector("hubbox-modal");
if (hubboxModal && singleWidgetManager.iframeElem) {
hubboxModal.locale = "en-GB"; // Match config locale
hubboxModal.open = false; // Start closed
hubboxModal.appendChild(singleWidgetManager.iframeElem); // Place iframe inside modal
}
3. Add an event listener to your "Choose Pickup Location" button/link:
const pickupButton = document.getElementById('choose-pickup-button');
const hubboxModal = document.querySelector("hubbox-modal");
const searchInput = document.getElementById('pickup-search-input'); // Optional search input
pickupButton.addEventListener('click', () => {
if (searchInput && searchInput.value) {
// Optional: Pre-fill search if the user has already entered a postcode
singleWidgetManager.events.emit(
singleWidgetManager.topics.emit.MAP_SEARCH_QUERY,
searchInput.value
);
}
if (hubboxModal) {
hubboxModal.open = true; // Open the modal
}
});
1. Ensure the container is initially hidden (e.g., via a CSS class):
<div id="hubbox-widget-container" style="display: none;"></div>
2. Add event listeners to your search input/button:
const searchButton = document.getElementById("hubbox-search-button");
const searchInput = document.getElementById("hubbox-search-input");
const widgetContainer = document.getElementById("hubbox-widget-container");
searchButton.addEventListener("click", function () {
const searchQuery = searchInput.value;
if (searchQuery && widgetContainer) {
singleWidgetManager.events.emit(
singleWidgetManager.topics.emit.MAP_SEARCH_QUERY,
searchQuery
);
widgetContainer.style.display = 'block'; // Show the widget container
// Or remove a hidden class: widgetContainer.classList.remove("hubbox-hidden");
}
});
Subscribe to events emitted by the SingleWidgetManager using events.subscribe to react to user actions within the widget iframe.
// --- Essential Callback: Collect Point Confirmed ---
const onCollectPointConfirmed = (messageAndTopic) => {
const collectPointData = messageAndTopic.message; // Contains selected location details
console.log("Pickup Point Confirmed:", collectPointData);
// 1. Populate frontend address fields
document.querySelector('#shipping-address1').value = collectPointData.address.street1 || '';
document.querySelector('#shipping-company').value = collectPointData.name || '';
document.querySelector('#shipping-address2').value = collectPointData.address.street2 || '';
document.querySelector('#shipping-city').value = collectPointData.address.city || '';
document.querySelector('#shipping-postcode').value = collectPointData.address.postcode || '';
document.querySelector('#shipping-state').value = collectPointData.address.region || '';
// Trigger change events if your checkout requires them for validation/updates
document.querySelector('#shipping-postcode').dispatchEvent(new Event('change', { bubbles: true }));
// 2. Send data to your backend (CRITICAL)
const payload = {
action: 'select_pickup',
collectPointId: collectPointData.id,
collectPointName: collectPointData.name,
collectPointNetwork: collectPointData.network, // e.g., 'ups', 'dpd', 'dhl'
collectPointType: collectPointData.type, // e.g., 'public', 'private'
address: collectPointData.address // Full address object
};
fetch('/your-checkout-api/update-shipping', {
method: 'POST',
headers: { 'Content-Type': 'application/json' /* Add CSRF token etc. */ },
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => {
console.log('Backend updated:', data);
// 3. Trigger shipping rate recalculation if needed
// yourPlatform.recalculateShippingRates();
// 4. Show confirmation UI (see Step 6)
showPickupConfirmation(collectPointData);
// 5. Close modal if using modal mode
const hubboxModal = document.querySelector("hubbox-modal");
if (hubboxModal) hubboxModal.open = false;
// 6. Optionally move to the next checkout step
// yourPlatform.goToNextStep();
})
.catch(error => console.error('Error sending pickup data to backend:', error));
};
// --- Essential Callback: Collect Point Unselected (user cancels/closes modal) ---
const onCollectPointUnselected = (messageAndTopic) => {
console.log("Pickup Point Unselected");
// 1. Clear frontend address fields (or restore original address)
// ... your logic to clear fields ...
// 2. Notify backend of deselection
fetch('/your-checkout-api/update-shipping', {
method: 'POST',
headers: { 'Content-Type': 'application/json' /* CSRF etc. */ },
body: JSON.stringify({ action: 'deselect_pickup' })
})
.then(() => {
console.log('Backend updated: Pickup deselected');
// 3. Trigger shipping rate recalculation (show home delivery rates)
// yourPlatform.recalculateShippingRates();
// 4. Hide confirmation UI
hidePickupConfirmation();
})
.catch(error => console.error('Error sending deselection to backend:', error));
};
// --- Other Useful Callbacks ---
const onWidgetReady = (messageAndTopic) => {
console.log("HubBox Widget Iframe Ready:", messageAndTopic.message);
// Widget iframe content is loaded; safe to emit initial search queries
};
const onBootFailed = (messageAndTopic) => {
console.error("HubBox Widget Boot Failed:", messageAndTopic.message);
// Handle errors - consider hiding the pickup option or showing an error message
};
const onCollectPointSelected = (messageAndTopic) => {
console.log("Point Selected (not yet confirmed):", messageAndTopic.message);
// Optional: Use for preliminary UI updates before confirmation
};
// Subscribe to events
singleWidgetManager.events.subscribe(singleWidgetManager.topics.subscribe.COLLECT_POINT_CONFIRMED, onCollectPointConfirmed);
singleWidgetManager.events.subscribe(singleWidgetManager.topics.subscribe.COLLECT_POINT_UNSELECTED, onCollectPointUnselected);
singleWidgetManager.events.subscribe(singleWidgetManager.topics.subscribe.WIDGET_READY, onWidgetReady);
singleWidgetManager.events.subscribe(singleWidgetManager.topics.subscribe.BOOT_FAILED, onBootFailed);
singleWidgetManager.events.subscribe(singleWidgetManager.topics.subscribe.COLLECT_POINT_SELECTED, onCollectPointSelected);
Implement robust logic within COLLECT_POINT_CONFIRMED and COLLECT_POINT_UNSELECTED to update frontend fields, communicate with your backend, and trigger necessary UI changes and recalculations.
For detailed event schemas and additional topics, see the /docs/manager-js.
After a user confirms a pickup point, display a clear confirmation message showing the selected location.

Use the <hubbox-pickup-confirmation-modern> component from @hubbox/web-components.
1. Add the component to your HTML (initially hidden):
<hhubbox-pickup-confirmation-modern
id="hubbox-confirmation"
class="hubbox-hidden"
locale="en-GB">
</hubbox-pickup-confirmation-modern>
2. Populate and show it in your onCollectPointConfirmed callback:
function showPickupConfirmation(collectPointData) {
const confirmationElement = document.getElementById('hubbox-confirmation');
if (confirmationElement) {
confirmationElement.address = collectPointData.address;
confirmationElement.openingTimes = collectPointData.openingTimes; // If available
confirmationElement.name = collectPointData.name;
// Optionally set map coordinates to show a map in the component
// confirmationElement.coords = {
// lat: collectPointData.address.latitude,
// lng: collectPointData.address.longitude
// };
confirmationElement.classList.remove('hubbox-hidden');
confirmationElement.style.display = 'block';
}
}
function hidePickupConfirmation() {
const confirmationElement = document.getElementById('hubbox-confirmation');
if (confirmationElement) {
confirmationElement.style.display = 'none';
confirmationElement.classList.add('hubbox-hidden');
}
}
Remember to call hidePickupConfirmation() inside your onCollectPointUnselected callback.
Build your own confirmation display using the collectPointData object received in the onCollectPointConfirmed callback. At a minimum, ensure you clearly display the selected location name and full address.
Your server-side checkout logic must handle the following responsibilities:
/your-checkout-api/update-shipping) that accepts the pickup selection data (ID, name, network, address, pickup flag) sent from the frontend.collectPointId, collectPointNetwork, collectPointName) with the order data in your database. This is required for fulfillment and manifesting.Once onCollectPointConfirmed fires, you must send the selected location's details to your server. Choose the method that best suits your checkout architecture.
Hidden Input Fields (Traditional Forms)
Add hidden <input> fields to your main checkout <form>. In the onCollectPointConfirmed callback, use JavaScript to populate these fields with the relevant HubBox data. When the user submits the form, this data is sent along with other form data.
Best for: Traditional multi-page checkouts relying on standard form submissions.
Not suitable for: SPA checkouts or headless setups where data is sent via APIs.
Example HTML:
<input type="hidden" name="shipping[hubbox_location_id]" id="hubbox-location-id">
<input type="hidden" name="shipping[hubbox_network]" id="hubbox-network">
<input type="hidden" name="shipping[hubbox_location_name]" id="hubbox-location-name">
Example JS (within onCollectPointConfirmed):
document.getElementById('hubbox-location-id').value = collectPointData.id || '';
document.getElementById('hubbox-network').value = collectPointData.network || '';
document.getElementById('hubbox-location-name').value = collectPointData.name || '';
On deselection (onCollectPointUnselected): Clear the values of these hidden fields.
Custom REST API Call (Recommended for SPAs/Headless)
Create a dedicated API endpoint on your backend server (e.g., /api/checkout/set-hubbox-selection). In the onCollectPointConfirmed callback, use fetch or XMLHttpRequest to POST the collectPointData as JSON to this endpoint.
Best for: Modern web applications (SPAs, headless). Allows for more complex data exchange and immediate backend validation.
Example JS:
function sendPickupDataToBackend(collectPointData) {
const payload = {
action: 'select_pickup',
collectPointId: collectPointData.id,
collectPointName: collectPointData.name,
collectPointNetwork: collectPointData.network,
collectPointType: collectPointData.type,
address: collectPointData.address
};
fetch('/api/checkout/set-hubbox-selection', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Include necessary headers like CSRF tokens, Auth tokens etc.
// 'X-CSRF-Token': getCsrfToken()
},
body: JSON.stringify(payload)
})
.then(response => {
if (!response.ok) { throw new Error('Network response was not ok'); }
return response.json();
})
.then(data => {
console.log('Backend successfully updated for pickup:', data);
// Trigger UI updates based on response if needed
// e.g., yourPlatform.recalculateShippingRates();
})
.catch(error => {
console.error('Error sending pickup selection to backend:', error);
// Handle error - consider reverting UI or showing an error message
});
}
Backend example concept (SFCC - for illustration only):
The backend endpoint needs to receive the JSON payload, validate it, update the cart/shipment object, and potentially recalculate shipping. The following is a conceptual example using SFCC server-side JS:
"use strict";
var server = require("server");
server.extend(module.superModule);
var csrfProtection = require("*/cartridge/scripts/middleware/csrf");
var HubBoxHelper = require("*/cartridge/scripts/helpers/HubBoxHelper");
var BasketMgr = require("dw/order/BasketMgr");
server.post("SetHubboxSelection", server.middleware.https, csrfProtection.validateAjaxRequest, function (req, res, next) {
var currentBasket = BasketMgr.getCurrentBasket();
if (!currentBasket) { /* Handle error */ }
var shipment = currentBasket.defaultShipment;
var requestData = JSON.parse(req.body);
if (requestData.action === 'select_pickup' && requestData.collectPointId) {
HubBoxHelper.copyHubBoxDataToShipment(
shipment,
requestData.collectPointId,
requestData.collectPointName,
requestData.collectPointType,
requestData.collectPointNetwork
);
res.json({ success: true, message: "HubBox selection saved." });
} else if (requestData.action === 'deselect_pickup') {
HubBoxHelper.clearHubBoxDataFromShipment(shipment);
res.json({ success: true, message: "HubBox selection cleared." });
} else {
res.setStatusCode(400);
res.json({ error: true, message: "Invalid request." });
}
return next();
});
module.exports = server.exports();
On deselection (onCollectPointUnselected): Make a similar fetch call to your endpoint with action: 'deselect_pickup' so the backend can clear the relevant data and revert to home delivery logic.
Cookies (Fallback Method)
In the onCollectPointConfirmed callback, use document.cookie to set a cookie containing the necessary HubBox data (e.g., location ID). Your backend code then reads this cookie value during checkout processing.
Best for: Last resort on highly restrictive platforms where modifying forms or making JS API calls is not possible.
Limitations:
HttpOnly and Secure flags where appropriate).On deselection (onCollectPointUnselected): Delete the cookie or set its value to indicate deselection.
The cookie method is generally not recommended unless other methods are not feasible for your platform.
Depending on your chosen carrier for pickup services, you may need to make changes to the way your WMS creates shipments and/or generates labels.
In some instances, you will need to take the id property from the COLLECT_POINT_CONFIRMED event and either place this code within the shipping address for the order or store it as metadata.
Refer to our documentation on Manifesting Pickup Orders for full details or contact HubBox if you require support.
Sharing details of completed pickup orders back to HubBox enables data analysis and reporting features within the HubBox Console dashboard. This provides insights into pickup adoption rates, ROI, carrier performance, and operational trends.
A server-to-server API call made after a customer successfully places a pickup order.
How it works: Your backend server makes a direct, secure API call to the HubBox Order Processing API endpoint after order placement.
Data sent: Order ID, collectPointId, collectPointNetwork, order value, currency, item count, and optionally anonymised customer details.
Pros: Most reliable method - server-to-server, less prone to browser issues (e.g., ad blockers), and supports retries on failure.
HubBox Order Processing API Documentation
A JavaScript snippet added to your Order Confirmation page.
How it works: When the confirmation page loads in the customer's browser, the script gathers order details and sends them directly to HubBox. Does not require backend changes specifically for HubBox data sharing.
Pros: Simpler frontend implementation.
HubBox Tracking Pixel Documentation
Key data points typically required:
collectPointId)collectPointNetwork)Data shared with HubBox is anonymised and does not include any personally identifiable information (PII) or payment/card information (PCI).
Choose the method that best fits your technical capabilities and reliability requirements. The Order Processing API is generally preferred for accuracy and resilience.
Launch and Confirmation Components
If using HubBox Components for the launch experience, style them using CSS ::part() and CSS Custom Properties (:root variables) as detailed in HubBox Components documentation.
Widget Modal/iframe
The internal look and feel of the map widget (colours, pins, map style within the iframe) is primarily controlled by the configId used during SingleWidgetManager initialisation. This configuration is managed within the HubBox platform. See Widget Configuration documentation.
Set Locale
Pass the correct locale code (e.g., "fr-FR") in iframeParams.locale when initialising SingleWidgetManager.
Override Text
Pass a translations object within iframeParams to override default text within the widget iframe. For text in components rendered directly on your page (such as the confirmation component), set the .translations property on the component instance after creating it. Refer to the Component Translations documentation for available translation keys.
Implementing features like eligibility and rate filtering with the SDK requires custom logic within your application, triggered by SDK events.
Prevent users from selecting pickup if their cart contains ineligible items.
Implementation:
function isBasketEligibleForPickup(cartItems) {
// Assumes 'isEligibleForPickup' is a boolean property on each cart item
return cartItems.every((item) => item.isEligibleForPickup);
}
const cartItems = getCartItems(); // Replace with your platform's method
const pickupOptionElement = document.getElementById("pickupOption");
if (isBasketEligibleForPickup(cartItems)) {
pickupOptionElement.style.display = "block"; // Show pickup option
} else {
pickupOptionElement.style.display = "none"; // Hide pickup option
}
For more information, see /docs/product-eligibility.
Show only relevant shipping rates after the user selects either Home Delivery or Local Pickup. This is essential for "Toggles" style launch experiences.
Implementation:
onCollectPointConfirmed callback, send a "pickup selected" flag (and optionally the chosen network/location details) to your backend API. In onCollectPointUnselected, send a "home delivery selected" flag.function filterShippingRates(availableRates, isPickupOrder) {
if (isPickupOrder) {
return availableRates.filter((rate) => rate.id === "local_pickup"); // Only keep "Local Pickup"
} else {
return availableRates.filter((rate) => rate.id !== "local_pickup"); // Filter out "Local Pickup"
}
}
const isPickupOrder = order.hasFlag("pickup"); // Replace with your logic
const allShippingRates = getAvailableShippingRates(); // Replace with your platform's method
const filteredRates = filterShippingRates(allShippingRates, isPickupOrder);
// Update the UI or recalculate totals using 'filteredRates'
For more information, see Shipping Rate Filtering documentation.
Thorough end-to-end testing on a staging or development environment is critical for SDK implementations.
Installation and Initialisation
WIDGET_READY events and confirm there are no BOOT_FAILED errors.UI and Widget Flow
Callback Logic
console.log within your callbacks (onCollectPointConfirmed, onCollectPointUnselected) to verify they fire correctly and receive the expected data (messageAndTopic.message).Backend Communication
fetch/AJAX calls from your JS callbacks reach your backend successfully.Filtering and Confirmation UI
Order Placement and Data
Cross-Browser and Device Testing
To access HubBox's UAT guide, see the Testing documentation.
An example project demonstrating basic SDK setup and interaction is available to download.
Ensure you are logged in to download. Contact your HubBox representative if you need access.
Download SDK Integration Example