# Salla 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](/miniapps/guides/sync) (`sync.resources` + `sync.webhooks.handlers`). ```javascript 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' }, }, }, }, }, }, }; ```