Build a Custom Shipping Carrier Integration for Sparkforge
Build a Marketplace app, register a shipping carrier with a callback URL, implement live rates and order webhooks, and post fulfillments back to Sparkforge. The full API flow is below.
This is a developer guide for building a custom shipping carrier integration on the Sparkforge Marketplace. The integration handles OAuth, live shipping rates at checkout, order webhooks, fulfillment, and uninstall cleanup.
What a custom carrier integration unlocks
- Flexibility: connect any carrier with an API.
- Accurate rates: show real-time costs from your carrier accounts at checkout.
- Automated fulfillment: create labels and shipments automatically when an order completes.
- Tracking sync: push tracking numbers back into Sparkforge orders.
1. Requirements
Technical knowledge
- Backend development (Node.js, Python, etc.)
- REST APIs, webhooks, and OAuth 2.0
Backend service
Your service needs to:
- Handle OAuth redirects from Sparkforge
- Receive webhooks from both Sparkforge (order events) and your carrier (shipping updates)
- Call Sparkforge and carrier APIs
- Serve a frontend UI (or host it separately)
Database
Securely store:
- Sparkforge OAuth access and refresh tokens, keyed by location ID
- Carrier credentials (API keys, secrets) per location
- Mapping between Sparkforge orders and carrier shipments
Frontend (Custom Page)
A simple UI where users initiate the connection, save carrier credentials, and manage settings.
API endpoints your backend exposes
- OAuth Redirect URI — handles Sparkforge OAuth callback
- Sparkforge Webhook Listener — receives
OrderCreate/OrderStatusUpdate - Carrier Credential Management — save/update/delete carrier credentials
- Carrier Webhook Listener — receives shipment updates from the carrier
- Live Rates Callback — Sparkforge calls this during checkout
2. Initial setup
Create a Marketplace app
Follow the Sparkforge Marketplace docs and create a new app.
- App Type: Sub-Account
- Redirect URI: your OAuth callback (e.g.,
https://your-backend.com/oauth/callback) - Webhook URL: your Sparkforge webhook listener (e.g.,
https://your-backend.com/webhooks/sparkforge) - Webhooks enabled:
OrderCreateandOrderStatusUpdate
Required scopes
orders.readonlyorders.writeshipping.readonlyshipping.write
Frontend Custom Page
Build a UI for capturing API credentials. Embed it via a Custom Menu Link pointing to your hosted page.
Handling user context
Sparkforge passes an encrypted token (typically a JWT or AES-encrypted payload) when loading your custom page. Decrypt it with your shared secret to get the locationId and userId.
// Backend (Node.js) — decrypt user context
const CryptoJS = require('crypto-js');
function decryptUserData(encryptedData, sharedSecret) {
const bytes = CryptoJS.AES.decrypt(encryptedData, sharedSecret);
const json = bytes.toString(CryptoJS.enc.Utf8);
return JSON.parse(json);
}
app.post('/your-context-endpoint', (req, res) => {
const { encryptedContextData } = req.body;
const sharedSecret = process.env.SPARKFORGE_APP_SHARED_SECRET;
const userData = decryptUserData(encryptedContextData, sharedSecret);
res.json({ success: true, locationId: userData.locationId });
});// Frontend — request context from Sparkforge parent window
async function getUserContext() {
return new Promise((resolve, reject) => {
const messageHandler = (event) => {
if (event.data?.message === 'REQUEST_USER_DATA_RESPONSE') {
window.removeEventListener('message', messageHandler);
resolve(event.data.payload);
}
};
window.addEventListener('message', messageHandler);
window.parent.postMessage({ message: 'REQUEST_USER_DATA' }, '*');
});
}Carrier credential handling
- Capture API key, secret, etc. from your frontend.
- Store securely, keyed by
locationId. - Validate with a test API call to the carrier.
- Use the validated credentials to register your carrier webhook listener URL with the carrier.
3. Live shipping rates
Register your carrier
Your backend posts to POST /shipping-carrier with the location context and carrier details.
POST /shipping-carrier HTTP/1.1
Authorization: Bearer <location_access_token>
Content-Type: application/json
{
"altId": "LOCATION_ID_HERE",
"altType": "location",
"name": "Your Custom Carrier Name",
"callbackUrl": "https://your-backend.com/rates"
}The response returns a _id (the shippingCarrierId). Store it alongside the location and credentials.
Implement the live rates callback
Sparkforge POSTs cart and destination details to your callbackUrl during checkout.
// Inbound rates request body
{
"rate": {
"altId": "LOCATION_ID_HERE",
"altType": "location",
"origin": { "name": "...", "address1": "...", "city": "...", "state": "...", "zip": "...", "country": "US" },
"destination": { "name": "...", "address1": "...", "city": "...", "state": "...", "zip": "...", "country": "US" },
"items": [
{ "name": "Product A", "sku": "SKU123", "quantity": 2, "grams": 500, "unitPrice": 25.0, "totalPrice": 50.0 }
],
"currency": "USD"
}
}Your backend:
- Parse the request body.
- Look up carrier credentials by
altId. - Translate to your carrier's API format.
- Call the carrier for live rates.
- Translate the carrier response into Sparkforge's expected format.
// Response back to Sparkforge
{
"rates": [
{ "serviceName": "Standard Ground", "amount": 15.5, "currency": "USD", "estimatedDays": 5 },
{ "serviceName": "Express Overnight", "amount": 45.0, "currency": "USD", "estimatedDays": 1 }
]
}Configure shipping zones
List zones with GET /shipping-zone?altId=...&altType=location. For each zone, create a carrier-backed rate:
POST /shipping-zones/{zoneId}/rates
{
"altId": "LOCATION_ID_HERE",
"altType": "location",
"name": "My Awesome Shipping - Live Rates",
"currency": "USD",
"amount": 0,
"conditionType": "NONE",
"isCarrierRate": true,
"shippingCarrierId": "SHIPPING_CARRIER_ID_HERE"
}4. Order syncing and fulfillment
Enable order webhooks
Confirm OrderCreate and OrderStatusUpdate are enabled in your app config and pointed at your Sparkforge webhook listener.
Handle the order webhook
app.post('/webhooks/sparkforge', async (req, res) => {
// 1. Validate webhook signature (recommended)
const { type, locationId, orderId, status } = req.body;
if ((type === 'OrderCreate' || type === 'OrderStatusUpdate') && status === 'completed') {
await processOrderForShipping(locationId, orderId);
}
res.status(200).send('ok');
});
async function processOrderForShipping(locationId, orderId) {
// 1. GET /orders/{orderId} to fetch full details
// 2. Look up carrier credentials for the location
// 3. Translate to carrier API format
// 4. Create the shipment with the carrier
// 5. Store carrierOrderId <-> sparkforgeOrderId mapping
}Handle carrier shipping updates
Your carrier webhook listener receives shipment updates (label printed, tracking generated, shipped, delivered). Validate the signature, parse the data, look up the matching Sparkforge order, and trigger fulfillment when a tracking number is available.
app.post('/webhooks/carrier', async (req, res) => {
const { orderReference, tracking, status } = req.body;
if (!tracking?.number) return res.status(200).send('no tracking yet');
// Look up sparkforgeOrderId + locationId from your DB by orderReference
if (status === 'SHIPPED' || status === 'LABEL_CREATED') {
await createSparkforgeFulfillment(locationId, sparkforgeOrderId, tracking.number, tracking.url);
}
res.status(200).send('ok');
});
async function createSparkforgeFulfillment(locationId, orderId, trackingNumber, trackingUrl) {
const payload = {
altId: locationId,
altType: 'location',
items: [/* line items being fulfilled */],
trackings: [{ trackingNumber, shippingCarrier: 'Your Custom Carrier Name', trackingUrl }],
notifyCustomer: true
};
// POST /orders/{orderId}/fulfillments with payload
}5. Disconnection and uninstallation
Listen for the App Uninstalled webhook from Sparkforge. When it fires:
- Look up the
shippingCarrierIdfor thatlocationId. - Call
DELETE /shipping-carrier/{shippingCarrierId}?altId=LOCATION_ID&altType=location. - Delete stored credentials and unregister carrier-side webhooks.
Removing the carrier registration prevents Sparkforge checkouts from calling a callback URL that no longer responds.
Build checklist
- Auth: store and refresh Sparkforge OAuth tokens; keep carrier credentials encrypted at rest.
- APIs: implement registration, rates, order fetch, and fulfillment correctly.
- Webhooks: validate signatures on both sides; idempotent handlers.
- Logging: log payloads (redacted) for every inbound webhook and outbound API call.
- Cleanup: handle uninstall cleanly so no orphaned carriers remain.
Frequently Asked Questions
What scopes does a shipping carrier integration need?
Where do I register my custom shipping carrier?
How does Sparkforge fetch live rates at checkout?
How do I post a fulfillment back to Sparkforge?
What should happen when the user uninstalls the app?
Was this article helpful?
Thanks for the feedback.