Quickstart
This page walks you through building a minimal but real ShopiMind integration, from an empty project all the way to the manifest you submit for registration. The path is deliberately as short as possible: you install the kit and the SDK, you write a skeleton integration, you run it locally, then you generate its manifest.
You write a defineIntegration({...}); the JavaScript integration kit (@shopimind/integration-kit-js) does the rest: webhook server, signature verification, sync scheduler, a store (SQLite by default, or your PostgreSQL, always inside your service; see Persistence & stores) and a ready-to-use, authenticated ShopiMind HTTP client on ctx.spm.
The kit depends on and re-exports the SDK (@shopimind/sdk-js): you import SDK resources and types from the kit, and you call the SDK's static methods on ctx.spm to push your data.
Before you start
- Node.js 18+ (both packages are ESM-only, Node ≥ 18.17).
- Some TypeScript knowledge. All the code on this page is in TypeScript.
- No ShopiMind credentials are required for this first walkthrough: you run the integration locally and generate its manifest offline.
1. Create the project
Create a folder, initialize package.json, then add TypeScript:
mkdir my-app && cd my-app
yarn init -y
yarn add -D typescript @types/nodeSince the kit and the SDK are ESM-only, your project must be too. In package.json, declare "type": "module" and a build script:
{
"name": "my-app",
"type": "module",
"scripts": {
"build": "tsc -b",
"start": "node dist/main.js",
"print:manifest": "node dist/print-manifest.js"
}
}A minimal tsconfig.json that emits modern ESM to dist/:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true
},
"include": ["src"]
}2. Install the kit, the SDK and dotenv
yarn add @shopimind/integration-kit-js better-sqlite3 @shopimind/sdk-js dotenv| Package | Role |
|---|---|
@shopimind/integration-kit-js | The integration runtime (^2.0.0): webhook server, HMAC signature, scheduler, SQLite store, lifecycle dispatch. It depends on and re-exports the SDK. |
better-sqlite3 | Driver for the default SQLite store (an optional peer dependency of the kit). Replace it with pg if you plug in PostgreSQL. |
@shopimind/sdk-js | The outbound HTTP client to the ShopiMind API (spm-api-key header). The kit builds the client for you and exposes it on ctx.spm; you call the SDK's static methods on it. |
dotenv | Loads your .env file into process.env at startup. |
Import the SDK from the kit
Because the kit re-exports the SDK (export * from '@shopimind/sdk-js'), import SDK resources and types (SpmCustomers, SpmEvents, SpmHelpers, …) directly from @shopimind/integration-kit-js. You never instantiate the SDK yourself.
3. Write the integration (src/integration.ts)
The integration is the heart of your project. You declare it with defineIntegration; the kit validates its shape at startup (valid slug, no duplicate sync entity…).
Start by typing your settings in src/settings.ts:
// src/settings.ts
import type { RawConfigs } from '@shopimind/integration-kit-js';
export interface MySettings {
apiKey: string;
}
export function parseSettings(raw: RawConfigs): MySettings {
return { apiKey: String(raw.api_key ?? '') };
}Then the integration itself. Here is a minimal skeleton; each optional field (provisioning, widgets, remoteData, hooks) will be detailed in the following pages:
// src/integration.ts
import { defineIntegration } from '@shopimind/integration-kit-js';
import type { Integration, IntegrationContext } from '@shopimind/integration-kit-js';
import type { MySettings } from './settings.js';
import { parseSettings } from './settings.js';
export const myIntegration: Integration<MySettings> = defineIntegration({
slug: 'my-app', // ^[a-z0-9_-]+$
meta: {
name: 'My App',
version: '1.0.0',
categories: ['crm'], // neutral slugs, resolved at registration
},
configSchema: {
steps: [
{
key: 'connection',
label: { fr: 'Connexion', en: 'Connection' },
fields: [
{
key: 'api_key',
type: 'password',
required: true,
sensitive: true,
label: { fr: 'Clé API', en: 'API key' },
},
],
on_complete: { action: 'test_connection' },
},
],
},
parseSettings,
testConnection: async (ctx: IntegrationContext<MySettings>) => Boolean(ctx.settings.apiKey),
syncSteps: [], // your sync steps — see data-sync
});Keep in mind
installationId(inctx) is an opaque token issued by ShopiMind: never interpret it.- You push your data through
ctx.spm, a raw, already-authenticatedSpmHttpClient. You call the SDK's static methods on it, for exampleSpmCustomers.bulkSave(ctx.spm, items, { chunk: true })orSpmEvents.trigger(ctx.spm, codeName, payload). In a sync step, prefer the safe push helperctx.sendBulk(...)(see Sync your data).
4. Wire up the runtime (src/main.ts)
The entry point maps your environment variables to the kit options, then starts the server. The kit builds the ShopiMind HTTP client itself: you do not instantiate the SDK and there is no gateway to wire up.
// src/main.ts
import 'dotenv/config';
import { createIntegrationApp } from '@shopimind/integration-kit-js';
import { myIntegration } from './integration.js';
const env = process.env;
const required = (key: string): string => {
const v = env[key];
if (!v) throw new Error(`Required environment variable: ${key}`);
return v;
};
const app = await createIntegrationApp(myIntegration, {
databasePath: env.DATABASE_PATH ?? './data/store.sqlite',
webhookSecret: required('WEBHOOK_SECRET'),
credentialsKey: env.CREDENTIALS_KEY ?? undefined,
allowPlaintextSecrets: env.NODE_ENV !== 'production', // dev only; prod requires CREDENTIALS_KEY
adminToken: env.ADMIN_TOKEN ?? undefined,
backfillDays: env.BACKFILL_DAYS ? Number(env.BACKFILL_DAYS) : 365,
syncIntervalMinutes: env.SYNC_INTERVAL_MINUTES ? Number(env.SYNC_INTERVAL_MINUTES) : 15,
port: env.PORT ? Number(env.PORT) : 8080,
...(env.SHOPIMIND_API_URL ? { spmBaseUrl: env.SHOPIMIND_API_URL } : {}),
});
await app.start().catch((e: unknown) => {
console.error('Integration startup failed', e);
process.exit(1);
});
process.on('SIGTERM', () => void app.stop().then(() => process.exit(0)));
process.on('SIGINT', () => void app.stop().then(() => process.exit(0)));createIntegrationApp builds the authenticated SpmHttpClient and exposes it on ctx.spm. The default ShopiMind API base is https://core.shopimind.com; pass spmBaseUrl (here from SHOPIMIND_API_URL) to override it.
ctx.spm is the raw SDK client
ctx.spm is a plain SpmHttpClient; you drive it with the SDK's static methods, all re-exported by the kit. The SDK never throws on an HTTP error: it returns an envelope { ok, statusCode, data, error }. Use SpmHelpers.unwrapOrThrow(env) or SpmHelpers.extractCounts(env) to handle it.
Environment variables (.env)
Create a .env file at the root of the integration:
WEBHOOK_SECRET= # HMAC, provided by ShopiMind at registration (required)
CREDENTIALS_KEY= # AES-256 (64 hex) — encryption of secrets at rest; REQUIRED in production
ADMIN_TOKEN= # protects the /admin/* routes
PORT=8080
SYNC_INTERVAL_MINUTES=15
BACKFILL_DAYS=365
SHOPIMIND_API_URL= # overrides the API base (default https://core.shopimind.com)
DATABASE_PATH=./data/store.sqliteFor this first run
WEBHOOK_SECRET is required: without it, the integration won't start. You'll receive it at registration (see step 6). To start locally right now, put any non-empty value in it; webhooks actually signed by ShopiMind will only be accepted with the real secret. In production, CREDENTIALS_KEY (64 hex) becomes required; otherwise the integration refuses to start (fail-closed). Set allowPlaintextSecrets: true only for local dev.
5. Run locally
Build, then start:
yarn build
yarn startThe server listens on port 8080 by default. Check that it's alive via the /health liveness probe (no authentication):
curl http://localhost:8080/health
# → {"status":"ok"}The kit exposes several HTTP routes. You have nothing to code on the server side: it serves them for you.
| Method | Path | Role |
|---|---|---|
POST | /webhook/receive | All lifecycle events (HMAC-signed) |
POST | /webhook/test-connection | Connection test from the ShopiMind UI (HMAC-signed) |
POST | /webhook/remote-data/{resource} | Dynamic options of a select (HMAC-signed) |
GET | /health | Liveness probe |
Verification of the incoming webhooks' HMAC X-Shopimind-Signature signature is done automatically by the kit. You have no signature validation to write.
6. Generate the manifest
The manifest is a neutral JSON describing your integration (slug, config, widgets, relative webhooks, lifecycle events). It's what you submit to ShopiMind for registration, and it contains no SQL, no secret, no absolute URL.
Add a small src/print-manifest.ts script:
// src/print-manifest.ts
import { buildIntegrationManifest } from '@shopimind/integration-kit-js';
import { myIntegration } from './integration.js';
console.log(JSON.stringify(buildIntegrationManifest(myIntegration), null, 2));The print:manifest script (declared in step 1) compiles and runs it. Redirect the output to a file:
yarn build
yarn print:manifest > integration.manifest.jsonSend this file to dev@shopimind.com for registration. ShopiMind then generates the webhook_secret (to place in WEBHOOK_SECRET), resolves your categories and finalizes the registration.
Your integration is running
You have an integration that starts, responds on /health, exposes its signed webhooks and produces its manifest. It doesn't push any data yet: that's the subject of the following steps.
What's next
- → The kit in detail: the full
defineIntegrationcontract, theIntegrationContext, thecreateIntegrationAppoptions and the store. - → Configuration: steps, fields, dynamic options (
remote),sensitiveandtest_connection. - → Sync your data. Declare
SyncSteps, manage the cursor and push safely viactx.sendBulkand the SDK statics onctx.spm. - → JavaScript SDK, the outbound HTTP client reference.