The Connect flow
Connectors obtain a key without the shop owner ever copying one. Two paths lead to the same result: a browser round-trip with PKCE, or a pairing code typed into the plugin. Both bind the key to the shop URL and the app, and both appear under Integrations › Connections in Wamia Shop.
Endpoint
| HTTP | URL |
|---|---|
| GET | https://api.wamia.tn/connect/authorize |
| POST | https://api.wamia.tn/connect/token |
| POST | https://api.wamia.tn/connect/pair |
| POST | https://api.wamia.tn/connect/revoke |
Authorisation with PKCE
Send the shop owner’s browser to GET /connect/authorize with client_id (your app code), an https redirect_uri matching the app’s registered patterns, the scope list, a state of at least 8 characters, a code_challenge (S256) and the shop URL. The shop owner signs in, reviews the scopes on the consent page and is redirected back with code and state.
Exchange the code at POST /connect/token with grant_type: authorization_code, the code_verifier, client_id, redirect_uri, your plugin_version and shop_url. The answer is the key and its budget; the code is single-use and expires after 60 seconds, so exchange it as soon as the browser returns.
Pairing code
When the plugin cannot open a browser round-trip (CLI installs, headless back offices), the shop owner generates a pairing code in the dashboard — Integrations › Connections › Pair a plugin — and types it into the plugin. The plugin posts it to POST /connect/pair with its client_id, shop_url and plugin_version and receives the same answer as the token endpoint. Codes live 10 minutes and are consumed on first use.
PKCE in TypeScript
import { createHash, randomBytes } from 'node:crypto';
const verifier = randomBytes(48).toString('base64url'); // 43–128 chars, [A-Za-z0-9-._~]
const challenge = createHash('sha256').update(verifier).digest('base64url');
const state = randomBytes(16).toString('hex');
const authorize = new URL('https://api.wamia.tn/connect/authorize');
authorize.searchParams.set('client_id', 'woocommerce');
authorize.searchParams.set('redirect_uri', 'https://shop.example.tn/wp-admin/admin.php?page=wamia');
authorize.searchParams.set('response_type', 'code');
authorize.searchParams.set('code_challenge', challenge);
authorize.searchParams.set('code_challenge_method', 'S256');
authorize.searchParams.set('scope', 'offers.manage catalog.media inventory.adjust orders.read orders.fulfil webhooks.manage');
authorize.searchParams.set('state', state);
authorize.searchParams.set('shop', 'https://shop.example.tn');
// Send the shop owner's browser to authorize.toString(); on return, verify state and exchange:
const token = await fetch('https://api.wamia.tn/connect/token', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code', code, code_verifier: verifier, client_id: 'woocommerce',
redirect_uri, plugin_version: '1.2.0', shop_url: 'https://shop.example.tn',
}),
}).then((r) => r.json());
// → { api_key: 'wamia_…', key_prefix, key_id, connection_id, organization_id, scopes[], expires_at,
// rate_limit: { reads_per_minute, writes_per_minute, bulk_per_minute } }
// Store api_key encrypted at rest: it is shown once. Rotate before expires_at (365 days).Pairing with curl
curl -sS https://api.wamia.tn/connect/pair \
-H 'content-type: application/json' \
--data '{ "code": "K7P2M9QX", "client_id": "prestashop", "shop_url": "https://shop.example.tn", "plugin_version": "1.0.0" }'
# → { "api_key": "wamia_…", "key_prefix": "…", "connection_id": "…", "organization_id": "…", "scopes": [ … ], "expires_at": "…", "rate_limit": { … } }POST /connect/token
| Name | Meaning |
|---|---|
| api_key | The secret, shown once. wamia_ prefix; store encrypted at rest. |
| key_prefix / key_id | Safe identifiers to log and show in your settings screen. |
| connection_id | The connection the shop sees in the dashboard. |
| organization_id | The store; every resource you touch belongs to it. |
| scopes | What was granted — it may be less than you asked for. |
| expires_at | 365 days from issue; rotate before then. |
| rate_limit | reads_per_minute, writes_per_minute, bulk_per_minute for this key kind. |
Key lifecycle
Binding
Connector keys are bound to the shop_url and the app. A call from another host is refused with UNAUTHENTICATED; a staging copy of the shop needs its own connection.
Rotation
Call apiRotateKey 30 days before expires_at (the dashboard reminds the shop too). The answer carries the new key; the old one keeps working for 24 hours so a half-finished deploy does not lose orders.
Minimum version
Wamia staff can raise the minimum supported version of a connector. Older plugins receive CONNECTOR_UPGRADE_REQUIRED (HTTP 426) with extensions.minSupportedVersion on every call until they update; show the update banner and stop the schedulers rather than retrying.
Revocation and disconnect
The shop can revoke a connection from the dashboard; the plugin can call POST /connect/revoke with the key. After either, every call answers CONNECTION_REVOKED. Plugins must then wipe the key, stop the schedulers and keep local data — Wamia never deletes a shop’s listings on disconnect.