Sandbox and testing
Build and test against ColiSwift’s pre-production with your own test seller and your own developer apps, then move orders forward to receive real webhooks.
The sandbox is ColiSwift’s own pre-production at https://coli.swiftmanager.net. It runs the same code as production with separate data: test sellers, test products, test orders. Production (https://coliswift.com) is the live platform with real sellers, real customers and real money — never test there.
| Sandbox | Production | |
|---|---|---|
| Partner API | https://coli.swiftmanager.net/api/partner/v1 | https://coliswift.com/api/partner/v1 |
| Token / revoke | https://coli.swiftmanager.net/api/oauth/token · /api/oauth/revoke | https://coliswift.com/api/oauth/token · /api/oauth/revoke |
| Consent page | https://coli.swiftmanager.net/oauth/authorize | https://coliswift.com/oauth/authorize |
| Seller account | Yours: active and with COD Drop access as soon as you sign up | Real sellers, validated by ColiSwift |
| App | Yours: you create it yourself on « Applications développeur » — no request needed | Created by ColiSwift after a review — see Go-live |
| Webhooks | Delivered to your app’s webhook URL | Delivered to your app’s webhook URL |
| Test helpers | POST /test/orders/{externalId}/advance, « Send test event » | None (/test/* answers 404) |
Getting set up
1. Create your test seller
- Sign up as a seller on
https://coli.swiftmanager.net/signup. On the sandbox the account is validated at once and its COD Drop access is opened — you can sign in immediately. (In production a new seller waits for ColiSwift’s validation, and COD Drop access is opened on request.) - Use your own test seller: there is one connection per app and seller, and an app you create can only be connected by the seller account that created it.
- Create a store and a few products — in ColiSwift, or with
POST /storesandPOST /productsonce connected. Note their references.
2. Create your app
Signed in as your test seller, open Intégrations → Applications développeur. This screen exists on the sandbox only.
- Create an app: name, description, logo, redirect URIs, the scopes it may ask for, a webhook URL and an optional event filter — the same fields ColiSwift registers for a production app (Apps and credentials).
- The
client_id, theclient_secretand the webhook secret are shown once, right after creation. Copy them then: only a hash of the client secret is kept. - Redirect URIs:
https:, orhttp://localhost/127.0.0.1/[::1]on any port. The webhook URL must be public HTTPS (never a private or loopback address): to receive webhooks on your laptop, use a tunnel. - Up to 5 active apps per seller account (
DEVELOPER_APP_LIMITbeyond that: delete one first). - Edit an app at any time. Removing a scope narrows the existing connections at once (see Apps and credentials).
- Rotate the client secret (the old one stops working immediately) or the webhook secret (the next deliveries are signed with the new one).
- Delete an app: every connection is revoked, every token dies and pending deliveries are marked dead. A deleted app cannot be edited again (
APP_DEACTIVATED); it frees a slot. - Each app card lists the last 20 webhook deliveries with their status (delivered, pending, failed, dead), attempt count and the HTTP status your endpoint answered.
3. Connect your seller to your app
Run the OAuth flow against https://coli.swiftmanager.net with your app’s client_id, one of its redirect URIs and PKCE. Sign in as your test seller on the consent page and approve.
4. Build and test
- Create orders with
POST /ordersand read them back. - On the sandbox nobody works your orders: move them forward yourself with the advance helper and receive the real
lead.*webhooks. - Check your webhook endpoint and its signature code with a test event.
- Test the failure paths: an expired access token (refresh), a reused refresh token (connection revoked), the seller disconnecting the app from their Integrations page (
401on the next call), a scope removed from your app (403on the calls it allowed).
Advance an order (test helper)
/test/orders/{externalId}/advanceMoves an order your app created one step through its life, the way ColiSwift’s teams would. Nothing is simulated: each step runs ColiSwift’s real status engine as the « Système ColiSwift » account (confirmation, validation and stock reservation, delivery), so the order and the webhooks you receive are exactly those of a real order. Sandbox only: in production the route does not exist (404).
tostringrequiredThe step to move to. Anything else is
400 VALIDATION_ERROR.confirmedshippeddeliveredreturnedcancelled
curl -X POST "https://coliswift.com/api/partner/v1/test/orders/shop_10482/advance" \
-H "Authorization: Bearer $COLISWIFT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "confirmed"
}'const res = await fetch('https://coliswift.com/api/partner/v1/test/orders/shop_10482/advance', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.COLISWIFT_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"to": "confirmed"
}),
});
const data = await res.json();$ch = curl_init('https://coliswift.com/api/partner/v1/test/orders/shop_10482/advance');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('COLISWIFT_ACCESS_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"to": "confirmed"
}
JSON,
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);import os, requests
res = requests.post(
"https://coliswift.com/api/partner/v1/test/orders/shop_10482/advance",
headers={"Authorization": f"Bearer {os.environ['COLISWIFT_ACCESS_TOKEN']}"},
json={
"to": "confirmed",
},
)
data = res.json(){
"from": "open",
"to": "confirmed",
"stage": "confirmed",
"allowedNext": [
"shipped",
"cancelled"
],
"order": {
"lead_id": "6f1c2a9e-3b7d-4c11-9a52-0d8e4b7f2c10",
"external_id": "shop_10482",
"order_code": "MA03112026-0042",
"currency": "MAD",
"country": "MA",
"store_id": "b2d4e6f8-1a3c-4e5f-8a9b-0c1d2e3f4a5b",
"status": "confirmed",
"confirmed_at": "2026-11-03T10:15:05.000Z",
"articles_amount": "398.00",
"lines": [
{
"line_id": "c0a8e1f2-5d6b-4a7c-9e8f-1a2b3c4d5e6f",
"product_id": "9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b",
"product_reference": "SERUM-ARGAN-30",
"product_name": "Sérum à l'huile d'argan 30 ml",
"variant_id": null,
"variant_label": null,
"quantity": 2,
"unit_price": "199.00"
}
],
"created_at": "2026-11-03T09:58:41.000Z",
"updated_at": "2026-11-03T10:15:05.000Z"
}
}fromstringThe order’s stage before the call.
tostringThe step you asked for.
stagestringThe order’s stage after the call.
allowedNextstring[]The steps you can ask for next (empty = final).
orderOrderThe order after the step, as
GET /orders/{externalId}returns it (Order object).
Steps and allowed paths
open → confirmed → shipped → delivered or returned; cancelled from open or confirmed. delivered, returned and cancelled are final. The stage is read from the order’s columns, so an order a ColiSwift user moved by hand is followed too; an order in a state the helper does not handle (e.g. a delivery problem) is closed and cannot be advanced.
| Step | What changes on the order | Same as | Main webhook |
|---|---|---|---|
confirmed | status: "confirmed", confirmed_at set: the order is confirmed and locked for shipping, stock reserved. Inside ColiSwift a warehouse holding stock for every line is chosen (in the delivery city when the order has one); a missing city / neighbourhood is filled with the warehouse’s zone. The work is recorded as done by « Système ColiSwift ». | A call-centre agent confirming and validating the order | lead.confirmation_status_changed |
shipped | status: "shipped": the parcel leaves with a driver. | The order put on a delivery slip | lead.delivery_status_changed |
delivered | status: "delivered"; inside ColiSwift the unit leaves the warehouse shelf. | The driver marking it delivered | lead.delivery_status_changed |
returned | status: "returned"; the stock reservation is released. | The driver marking it returned | lead.delivery_status_changed |
cancelled | status: "cancelled"; from confirmed, the reservation is released. | An administrator’s cancellation | lead.confirmation_status_changed |
Each step fires exactly the lead.* events the real gesture fires (one change can produce several, see order events). They are delivered like any event: to your app’s webhook URL, signed, while the seller’s connection is live.
Errors
| HTTP | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | to missing or not one of the five steps. |
| 403 | INSUFFICIENT_SCOPE | The token lacks orders:write. |
| 404 | NOT_FOUND | No order with this externalId created by your app for this seller — another seller’s order, another app’s order or a manual order is « not found », never « forbidden ». Also the answer on production, where the helper does not exist. |
| 409 | ILLEGAL_TRANSITION | The step is not allowed from the current stage. The message names the stage and the allowed next steps (« Cannot move this order from "open" to "delivered". Allowed next: confirmed, cancelled. »), or says it is final. |
| 409 | SANDBOX_NO_STOCK | No warehouse holds stock for every line of the order (in its delivery city, when it has one). |
| 409 | SANDBOX_NO_ZONE | The chosen warehouse has no active city and neighbourhood to give the order. |
| 409 | ORDER_IN_CALL | A ColiSwift agent is calling this customer right now. |
| 409 | ORDER_CHANGED | The order changed during the call; retry. |
| 409 | COURIER_MANAGED | Orders of this country are handled by a courier partner and cannot be advanced here. |
| 409 | LEAD_ALREADY_INVOICED | The order is already on an invoice; its delivery status cannot change. |
| 503 | SANDBOX_SYSTEM_USER_MISSING | The sandbox is misconfigured; tell ColiSwift. |
A refusal of ColiSwift’s status engine itself (for example a line out of stock at confirmation) is answered as that engine answers it, and the order is left as it was.
Send a test event
On your app’s card, « Envoyer un événement de test » queues one test.ping event for that app only. It goes through the normal pipeline: signed with your app’s webhook secret (X-Coliswift-Signature), sent with the usual headers, retried on failure, listed in the app’s deliveries a few seconds later. It needs a webhook URL on the app (NO_WEBHOOK_URL otherwise), but no seller connection. At most one every 10 seconds per app and 30 per hour per seller (429 TEST_EVENT_RATE_LIMITED).
{
"data": {
"client_id": "cs_client_0f1e2d3c4b5a69788796a5b4c3d2e1f0",
"message": "Test event from the ColiSwift sandbox. Verify X-Coliswift-Signature with your webhook secret."
},
"event": "test.ping",
"event_id": "evt_5f1c9a0b2d3e4f5a6b7c8d9e",
"occurred_at": "2026-11-02T11:20:41.302Z",
"seller_id": "6a1c2f0e-3b7d-4c55-9e10-2f8a4b7c9d01",
"sequence": 4201
}seller_id is your test seller, data.client_id your app. test.ping is not in any scope and never reaches another app; acknowledge it with 2xx like any event.
Self-checks
- Send the same
POST /orderstwice: the second answer must be200with"duplicate": true. - Send a price as a JSON number: you must get
400 VALIDATION_ERROR— then fix your encoder. - Call with a token whose scopes lack the endpoint’s:
403 INSUFFICIENT_SCOPEwithrequired(request a narrowerscopeon the authorize URL to try it). - Send a test event and check that your endpoint accepts its signature — then rotate the webhook secret and check that it rejects a body signed with the old one.
- Walk one order to
deliveredand one toreturnedwith the advance helper, and check that your copy matchesGET /orders/{externalId}at every step.

