Lifecycle & webhooks
ShopiMind notifies your server of a shop's events via signed webhooks. You declare one URL per event; ShopiMind sends a POST to it.
Signature verification (HMAC)
Each webhook carries two headers:
X-Shopimind-Signature : <hex sha256>
X-Shopimind-Timestamp : <unix seconds>The signature is:
HMAC-SHA256("<timestamp>.<raw body>", WEBHOOK_SECRET) → hex digestVerify on the raw bytes
Compute the HMAC over the exact raw body of the request (before any JSON.parse), otherwise the signature will not match. Compare in constant time.
const crypto = require('crypto');
function verify(rawBody, signature, timestamp, secret, toleranceSeconds = 300) {
// Anti-replay: reject requests that are too old
const skew = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (skew > toleranceSeconds) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const a = Buffer.from(signature, 'hex');
const b = Buffer.from(expected, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}The WEBHOOK_SECRET is provided to you by ShopiMind when your integration is registered. The clock tolerance (anti-replay) is up to you — 5 minutes is a safe value.
If verification fails, respond with 401. ShopiMind does not retry 4xx errors.
Response contract
Every webhook (except OAuth steps) must respond with:
- HTTP
200exactly —201/204are treated as a failure; - a JSON body containing
{ "success": true }.
If processing is impossible, return 200 with { "success": false, "error": "…" }: ShopiMind handles the failure without "retry-bombing" you.
| Event | Retry on failure |
|---|---|
install | None — a failure triggers a rollback on the ShopiMind side (installation cancelled, key deleted) |
others (activate, deactivate, …) | 1 retry (~2 s) on a transient error (timeout, 5xx, connection) |
The events
All lifecycle events arrive on the same URL (install, uninstall, activate, deactivate, config_updated), distinguished by the event field.
install — Type A
{
"event": "integration.installed",
"id_shop": 12345,
"id_shop_integration": 678,
"integration_slug": "my-integration",
"shop_domain": "https://shop.example.com",
"shop_name": "Boutique Example",
"access_token": "int4f2a9.dGhpcy1pcy1hLXNlY3JldA",
"installed_at": "2026-05-27T10:15:00.000Z"
}Persist the access_token (API key) and the id_shop_integration immediately. Initial status: inactive (the key is still revoked).
activate
{
"event": "integration.activated",
"id_shop": 12345,
"id_shop_integration": 678,
"integration_slug": "my-integration",
"activated_at": "2026-05-27T10:20:00.000Z",
"configs": { "api_url": "https://...", "api_token": "valeur-déchiffrée" }
}The configs are transmitted decrypted. From here on, your access_token is usable to call the ShopiMind API.
config_updated
{
"event": "integration.config_updated",
"id_shop_integration": 678,
"integration_slug": "my-integration",
"configs": { "...": "..." },
"updated_at": "2026-05-27T10:35:00.000Z"
}deactivate / uninstall
{ "event": "integration.deactivated", "id_shop_integration": 678, "deactivated_at": "..." }{ "event": "integration.uninstalled", "id_shop_integration": 678, "uninstalled_at": "..." }After deactivate, the API key is re-revoked; after uninstall, the configuration is deleted and the key permanently revoked.
Type B — OAuth
Use this if the user must first authenticate with you. The flow:
The user clicks "Install" in ShopiMind.
ShopiMind redirects the browser to your consent page (
oauth_connect_url) with two parameters:state(opaque token, 10 min lifetime) andredirect_uri.You authenticate the user (your own UI).
You redirect the browser to the provided
redirect_uri, sending back thestateunchanged and addingexternal_account_id(+external_account_name):<redirect_uri>?state=<unchanged>&external_account_id=acct_123&external_account_name=John%27s%20AccountOn denial:
<redirect_uri>?state=<unchanged>&error=access_denied.ShopiMind validates the
state, generates the API key and sends you theinstallwebhook — the OAuth variant, withoutid_shoporid_shop_integration:json{ "event": "integration.installed", "external_account_id": "acct_123", "external_account_name": "Compte de Jean", "shop_domain": "https://shop.example.com", "access_token": "int4f2a9.…", "installed_at": "2026-05-27T10:15:00.000Z" }
Type B specifics
- Index the installation on
external_account_iduntilactivatebrings theid_shop_integration. - Pass the
stateas-is (do not decode it, do not log it). - Never hardcode the
redirect_uri— use the one received on each call.
The starter implements this flow end to end (src/routes/oauth.js).
Configuration hooks
Two additional webhooks serve the ShopiMind configuration interface — detailed in Configuration:
test_connection— verify connectivity with the entered credentials.remote-data/<resource>— dynamically populate dropdown lists.