An e-commerce platform with OAuth 2.0 auth, customer extraction, and order/cart handlers.
Legacy block in snippet
This example includes deprecated webhook.ecommerce for illustration. New submissions must use Sync (sync.resources + sync.webhooks.handlers).
const sallaMiniApp = {
ns: 'salla',
name: 'salla',
type: 'salla',
title: 'Salla',
label: 'Salla',
description: 'Sync orders, customers, and abandoned carts from Salla.',
shortDescription: 'Salla ecommerce: orders, customers, carts',
status: 'public',
version: '1.0.0',
category: 'ecommerce',
icon: 'https://cdn.karzoun.com/miniapps/salla/icon.svg',
logo: 'https://cdn.karzoun.com/miniapps/salla/logo.svg',
docsUrl: 'https://docs.salla.dev',
auth: {
type: 'oauth2',
sensitiveKeys: ['accessToken', 'refreshToken', 'webhookSecret'],
config: {
accessToken: '',
refreshToken: '',
webhookSecret: '',
},
userDetails: {
url: 'https://api.salla.dev/admin/v2/oauth2/user/info',
method: 'GET',
headers: {
Authorization: 'Bearer [[accessToken]]',
'Content-Type': 'application/json',
},
mapping: { uid: '$.data.id', name: '$.data.name' },
},
get_token: {
url: 'https://accounts.salla.sa/oauth2/token',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
bodyType: 'json',
body: {
client_id: '{{client_id}}',
client_secret: '{{client_secret}}',
grant_type: 'authorization_code',
code: '{{code}}',
redirect_uri: '{{redirect_uri}}',
},
mapping: {
accessToken: '$.access_token',
refreshToken: '$.refresh_token',
expiresIn: '$.expires_in',
},
},
refresh_token: {
url: 'https://accounts.salla.sa/oauth2/token',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
bodyType: 'json',
body: {
client_id: '{{client_id}}',
client_secret: '{{client_secret}}',
grant_type: 'refresh_token',
refresh_token: '[[refreshToken]]',
},
mapping: {
accessToken: '$.access_token',
refreshToken: '$.refresh_token',
},
},
auto_refresh: true,
registrationRequests: [],
},
triggers: [
{ type: 'miniapps:salla', event: 'order.created', icon: 'shopping-cart',
label: 'Order Created', description: 'New order placed', isCustom: false, img: 'automation3.svg' },
{ type: 'miniapps:salla', event: 'order.status.updated', icon: 'shopping-cart',
label: 'Order Status Changed', description: 'Order status updated', isCustom: false, img: 'automation3.svg' },
{ type: 'miniapps:salla', event: 'abandoned.cart', icon: 'shopping-cart',
label: 'Cart Abandoned', description: 'Customer abandoned their cart', isCustom: false, img: 'automation3.svg' },
{ type: 'miniapps:salla', event: 'customer.created', icon: 'users-alt',
label: 'Customer Created', description: 'New customer registered', isCustom: false, img: 'automation3.svg' },
// ... more triggers
],
actions: [], // E-commerce apps typically have triggers only
source: {},
webhook: {
// Event name is in the body
eventExtraction: {
source: 'body',
path: '$.event',
},
// Dedup using data ID
transactionId: {
path: '$.data.id',
fallback: 'generate',
},
// HMAC-SHA256 verification
// Salla uses a global webhook secret (set in partner dashboard),
// so the secret is stored in auth.config.webhookSecret (not per-tenant)
verification: {
type: 'hmac-sha256',
headerName: 'X-Salla-Signature',
secretKey: 'webhookSecret', // Resolves from auth.config (global secret)
},
// Acknowledge immediately
response: { statusCode: 200, body: { ok: true } },
// Auto-extract customer from webhook payload
customerExtraction: {
basePath: '$.data.customer',
mapping: {
firstName: 'first_name',
lastName: 'last_name',
primaryEmail: 'email',
primaryPhone: 'mobile',
code: 'id',
},
overrides: {
'customer.created': { basePath: '$.data' },
},
},
// E-commerce data processing
ecommerce: {
platform: 'salla',
// Pipeline stages
stageMappings: {
orders: {
type: 'static',
stages: [
{ name: 'Pending', code: 'pending', externalStatusCode: 'pending', probability: '10', order: 0 },
{ name: 'Processing', code: 'processing', externalStatusCode: 'processing', probability: '30', order: 1 },
{ name: 'Shipped', code: 'shipped', externalStatusCode: 'shipped', probability: '60', order: 2 },
{ name: 'Delivered', code: 'delivered', externalStatusCode: 'delivered', probability: '90', order: 3 },
{ name: 'Completed', code: 'completed', externalStatusCode: 'completed', probability: '100', order: 4 },
{ name: 'Cancelled', code: 'cancelled', externalStatusCode: 'cancelled', probability: '0', order: 5 },
],
},
abandonedCarts: {
type: 'static',
stages: [
{ name: 'Abandoned', code: 'abandoned', externalStatusCode: 'abandoned', probability: '10', order: 0 },
{ name: 'Recovered', code: 'recovered', externalStatusCode: 'recovered', probability: '50', order: 1 },
{ name: 'Completed', code: 'completed', externalStatusCode: 'completed', probability: '100', order: 2 },
],
},
},
// Handler per webhook event
handlers: {
'order.created': {
recordType: 'order',
operation: 'create',
mapping: {
code: '$.data.id',
orderNumber: '$.data.reference_id',
status: '$.data.status.name',
totalAmount: '$.data.amounts.total.amount',
currency: '$.data.amounts.total.currency',
paymentMethod: '$.data.payment_method',
discountCode: { path: '$.data.discounts', transform: 'joinCodes' },
items: { path: '$.data.items', transform: 'mapItems' },
},
},
'order.status.updated': {
recordType: 'order',
operation: 'update',
mapping: {
code: '$.data.id',
status: '$.data.status.name',
},
},
'abandoned.cart': {
recordType: 'abandonedCart',
operation: 'create',
mapping: {
code: '$.data.id',
totalAmount: '$.data.total.amount',
currency: '$.data.total.currency',
checkoutUrl: '$.data.checkout_url',
state: 'abandoned',
items: { path: '$.data.items', transform: 'mapItems' },
},
},
},
},
},
};