Send Swell orders to Sendcloud and get tracking back. Orders show up in Sendcloud's Incoming Orders, where you create labels. When a label is created, the app saves the tracking on the order and fulfills it in Swell.
- Orders to Sendcloud: when an order is paid (or submitted, see settings) and has items to ship, it's sent to Sendcloud through the v3 Orders API (
POST /api/v3/orders). It includes the shipping and billing address, customer, line items with SKU, prices and weights, order totals, and the checkout shipping service name. Sending the same order again updates it in Sendcloud rather than duplicating it. - Canceled orders: when a Swell order is canceled, it's removed from Sendcloud's Incoming Orders.
- Tracking back to Swell: Sendcloud's
parcel_status_changedwebhook updates the order with the parcel status, tracking number, tracking link and carrier. Requests are verified with theSendcloud-SignatureHMAC header, and updates that arrive out of order are ignored. - Fulfillment: once a parcel has a label, the app creates a Swell shipment with the carrier and tracking number for everything still to be shipped. If the parcel is later canceled, that shipment is canceled too.
- Order tab: a Sendcloud tab on the order page shows the Sendcloud order ID, status, parcel ID, carrier, tracking number, tracking link and the last error.
- Webhook URL pattern in settings: the settings show the webhook URL pattern as a read-only value:
https://{store-id}:{public-key}@{store-id}.swell.store/functions/sendcloud/sendcloud-webhook.
All credentials and options are app settings (settings/sendcloud.json). Nothing is hard-coded.
| Setting | Required | Default | Description |
|---|---|---|---|
| Public Key | yes | Public key of your Sendcloud API integration. | |
| Secret Key | yes | Secret key of the same integration. Also verifies webhook signatures. | |
| Integration ID | yes | ID of the Sendcloud API integration. Orders are created under it. Shown at the end of the integration's URL in the Sendcloud panel. | |
| Webhook URL | read-only | https://{store-id}:{public-key}@{store-id}.swell.store/functions/sendcloud/sendcloud-webhook |
The URL pattern. Replace {store-id} and {public-key} before pasting it into Sendcloud. |
| Webhook Signature Key | no | Only if your Sendcloud integration shows a separate Webhook Signature Key. Otherwise the Secret Key is used. | |
| Send orders to Sendcloud when | no | Order is paid | Order is paid (order.paid) or Order is submitted (order.submitted). |
| Default shipping option code | no | Preselects a Sendcloud shipping option on each order, e.g. postnl:standard. |
|
| Default parcel weight (kg) | no | 1 | Used when the order's items have no shipment weight. |
| Create Swell shipment when label is created | no | on | Fulfill the Swell order with the Sendcloud tracking number. |
| Enabled | no | on | Turns off sending orders and cancellations without uninstalling the app. |
- In Sendcloud, go to Settings → Integrations and add a Sendcloud API integration. Copy its public and secret keys.
- Install the app in Swell and open its settings. Enter the Public Key, Secret Key and Integration ID.
- Build the webhook URL from the pattern in the app settings,
https://{store-id}:{public-key}@{store-id}.swell.store/functions/sendcloud/sendcloud-webhook. Replace{store-id}with your store ID and{public-key}with your Swell public key (Developer → API keys). Use the live key for the live environment and the test key for test. - In the Sendcloud integration, paste the URL into Webhook URL, turn on webhook feedback, and save. Test API Webhook should return 200.
- Place a test order and pay it. It should appear in Sendcloud's Incoming Orders, and its Sendcloud tab in Swell should show a Sendcloud order ID.
- Create a label in Sendcloud. The Swell order should get the tracking number and a shipment.
| Function | Trigger | What it does |
|---|---|---|
functions/order-events.ts |
order.submitted, order.paid, order.canceled |
Sends the order to Sendcloud on the event chosen in settings. Deletes the Sendcloud order on cancel. |
functions/sendcloud-webhook.ts |
Public route, POST |
Receives Sendcloud webhooks and updates the order and shipment. |
functions/lib/sendcloud.ts |
Shared | Sendcloud API client, Swell order to Sendcloud order mapping, signature check. |
Data stored on each order under $app.sendcloud (models/orders.json): sendcloud_order_id, sendcloud_parcel_id, sendcloud_status_id, sendcloud_status, sendcloud_status_timestamp, sendcloud_tracking_number, sendcloud_tracking_url, sendcloud_carrier, sendcloud_shipment_id, sendcloud_error.
Weights: Swell item weights are line totals in the store's weight unit (/settings/shipments → weight_unit, default lb). They're converted to kg per unit for each item and summed for the whole order.
House numbers: Sendcloud needs the street and house number separately. The first address line is split on a leading number (417 Montgomery St) or a trailing one (Stationsstraat 12A). If neither matches, the whole line is sent as the street.
- No label creation: the app doesn't create labels itself. Labels are created in Sendcloud. Sendcloud's v2 Parcels API is blocked for newer accounts, and v3 labels need the Shipments API, which isn't implemented yet.
- No checkout rates: customers don't see Sendcloud shipping rates at checkout. That needs a separate shipping extension.
- One parcel per order: only one parcel is tracked per order. A second parcel replaces the first only after the first is canceled. Multi-parcel (multicollo) shipments aren't tracked separately.
- Order matching: webhook updates are matched to Swell orders by order number.
- Cancel doesn't cancel labels: canceling a Swell order removes it from Sendcloud's Incoming Orders but doesn't cancel a label that was already created.
- Currencies: Sendcloud's v3 Orders API documents only EUR, GBP and USD prices. Orders in other currencies may be rejected, and the error is shown on the order.
- Retries: errors from Sendcloud are saved to the order's
sendcloud_error. Only Sendcloud 5xx errors are retried. Model-event functions that keep failing are turned off by the platform after about 4 days.
Needs the Swell CLI (swell login) and Node 20+.
npm install
swell app dev # run functions locally against the test environment
npm test # vitest: unit tests + read-only integration tests
npm run typecheck
swell app push # deploy to the store's test environment
swell inspect functions --app=.
swell logs --type function -s sendcloud- Unit tests (
test/unit/) mock Swell and Sendcloud (fetch) and cover every function and the shared library. - Integration tests (
test/integration/) are read-only and use yourswell loginsession against the test store. - CLI test scaffold: it pins
vitest3.2 but asks for the latest@cloudflare/vitest-pool-workers, which now needs vitest 4. The pool is pinned to~0.12.21, the last version that works with vitest 3.2. - Public route URL: the webhook only responds after
swell app push. Underswell app devit isn't reachable publicly. - Webhook URL: Sendcloud reaches the route at
https://{store-id}:{public-key}@{store-id}.swell.store/functions/sendcloud/sendcloud-webhook. That's the store host, with no/api, and the key in the URL. Without a key the request returns 404. - Webhook signatures: the platform doesn't pass the original request bytes to the function.
req.rawBodyarrives re-serialized asJSON.stringify(body, null, 2). So the handler also checks the signature against the parsed body serialized the way Python'sjson.dumps()does it (toPythonJson), and against compact JSON. It logs which one matched. Numbers that Sendcloud sends as floats with a trailing.0can't be rebuilt exactly and would fail verification.
Listing assets: assets/screenshots/*.png are 3200×1800 listing images that show the Swell dashboard (light theme) with the app on a sample store ("Alder & Co."), referenced from images in swell.json. assets/icon.png is Sendcloud's official app icon (from sendcloud.com), made full-bleed at 512×512, and assets/image.png is a 1200×630 social card.