Tutorial 12 min read

Complete Guide to Salla Store Automation

Mahmoud Hamdy
March 28, 2026

When I built the IAI Salla Bot for a Gulf-based merchant, I discovered that automation is not just a nice-to-have — it is the difference between a store that scales and one that drowns its owner in manual work. This guide covers every major automation opportunity inside the Salla platform: from processing orders automatically, to syncing inventory in real time, to recovering abandoned carts without lifting a finger. All code examples are in Node.js and match what I used in production.

Understanding the Salla API

Salla exposes a well-documented REST API at https://api.salla.dev/admin/v2. Authentication uses OAuth 2.0 — you register your app in the Salla Partner Portal, get your client credentials, and exchange them for access and refresh tokens. Tokens expire after one hour, so you need a refresh strategy baked in from day one.

The API covers six major resource groups: Orders, Products, Customers, Coupons, Shipments, and Webhooks. Each resource supports standard CRUD, filtering, sorting, and pagination. Rate limits sit at 60 requests per minute per token — generous for automation, but you will hit them during high-traffic periods if you poll naively.

// lib/salla-auth.ts
import axios from 'axios';

interface TokenStore {
  accessToken: string;
  refreshToken: string;
  expiresAt: number;
}

let tokenStore: TokenStore | null = null;

export async function getAccessToken(): Promise {
  if (tokenStore && Date.now() < tokenStore.expiresAt - 60_000) {
    return tokenStore.accessToken;
  }
  const response = await axios.post('https://accounts.salla.sa/oauth2/token', {
    grant_type: 'refresh_token',
    refresh_token: tokenStore?.refreshToken ?? process.env.SALLA_REFRESH_TOKEN,
    client_id: process.env.SALLA_CLIENT_ID,
    client_secret: process.env.SALLA_CLIENT_SECRET,
  });
  tokenStore = {
    accessToken: response.data.access_token,
    refreshToken: response.data.refresh_token,
    expiresAt: Date.now() + response.data.expires_in * 1000,
  };
  return tokenStore.accessToken;
}

Automating Order Management

Order management is the highest-leverage automation. When a new order lands, you typically need to: validate stock, assign it to a fulfilment queue, generate a packing slip, notify the relevant team, and confirm to the customer. Doing this manually is error-prone and slow. With webhooks, you can do it in under a second.

Salla sends an order.created event the moment a customer completes checkout. Here is a production-grade handler that covers all the steps:

// handlers/order-created.ts
import { getAccessToken } from '../lib/salla-auth';
import { notifyFulfillmentTeam, generatePackingSlip } from '../lib/fulfillment';
import { sendOrderConfirmation } from '../lib/notifications';
import axios from 'axios';

export async function handleOrderCreated(order: SallaOrder) {
  const token = await getAccessToken();
  const client = axios.create({
    baseURL: 'https://api.salla.dev/admin/v2',
    headers: { Authorization: `Bearer ${token}` },
  });

  // 1. Verify stock is still available
  for (const item of order.items) {
    const { data } = await client.get(`/products/${item.product.id}`);
    if (data.data.quantity < item.quantity) {
      // Flag order for manual review
      await client.put(`/orders/${order.id}`, { status: 'under_review' });
      await notifyFulfillmentTeam({ type: 'stock_issue', order });
      return;
    }
  }

  // 2. Move to processing
  await client.put(`/orders/${order.id}`, { status: 'in_progress' });

  // 3. Generate packing slip PDF
  const slipUrl = await generatePackingSlip(order);

  // 4. Notify fulfilment team via Telegram
  await notifyFulfillmentTeam({ type: 'new_order', order, slipUrl });

  // 5. Send customer confirmation
  await sendOrderConfirmation(order);
}

Automating Customer Support

In my experience building the IAI bot, customer support messages fall into four categories that can be fully automated: order status enquiries, product availability questions, return requests, and shipping address changes. Together these account for roughly 80% of incoming support tickets on a typical Salla store.

The support bot connects to the Salla API to answer in real time. A customer sends their order number, the bot fetches the current status, translates it into plain Arabic or English, and replies instantly — no human needed.

// bot/support-handler.ts
import { SallaClient } from '../lib/salla-client';

const STATUS_MAP: Record = {
  pending:     { en: 'Payment pending',   ar: 'في انتظار الدفع' },
  in_progress: { en: 'Being processed',   ar: 'قيد المعالجة' },
  shipped:     { en: 'Shipped',           ar: 'تم الشحن' },
  delivered:   { en: 'Delivered',         ar: 'تم التسليم' },
  cancelled:   { en: 'Cancelled',         ar: 'ملغي' },
};

export async function handleOrderStatusQuery(
  orderId: string,
  lang: 'en' | 'ar'
): Promise {
  const client = new SallaClient(await getAccessToken());
  const order = await client.getOrder(orderId);
  const statusText = STATUS_MAP[order.status.slug]?.[lang] ?? order.status.name;
  const total = `${order.amounts.total.amount} ${order.currency}`;

  if (lang === 'ar') {
    return `طلبك رقم #${order.reference_id}\nالحالة: ${statusText}\nالإجمالي: ${total}`;
  }
  return `Order #${order.reference_id}\nStatus: ${statusText}\nTotal: ${total}`;
}

Real-Time Inventory Sync

If you sell the same products across Salla and another channel (your own website, Amazon, or a physical shop), inventory drift is a serious risk. The solution is bidirectional sync: when a Salla order reduces inventory, push the new quantity to the other channel; when the external channel updates, push back to Salla.

// sync/inventory-sync.ts
export async function syncInventoryToSalla(
  productSku: string,
  newQty: number
): Promise {
  const token = await getAccessToken();
  const client = axios.create({
    baseURL: 'https://api.salla.dev/admin/v2',
    headers: { Authorization: `Bearer ${token}` },
  });

  // Find product by SKU
  const { data } = await client.get('/products', {
    params: { sku: productSku, per_page: 1 },
  });
  const product = data.data?.[0];
  if (!product) {
    console.warn(`[InventorySync] SKU ${productSku} not found in Salla`);
    return;
  }

  await client.put(`/products/${product.id}`, { quantity: newQty });
  console.log(`[InventorySync] Updated ${productSku} → qty ${newQty}`);
}

// Called from Salla webhook: order.created
export async function decrementInventory(order: SallaOrder): Promise {
  for (const item of order.items) {
    await syncInventoryToSalla(item.product.sku, item.product.quantity - item.quantity);
  }
}

Abandoned Cart Recovery

Cart abandonment rates on Gulf e-commerce stores average 68-72%. Even recovering 10% of those carts is significant revenue. Salla exposes abandoned cart data through the GET /carts/abandoned endpoint. The typical recovery sequence is: send a reminder at 1 hour, a discount code at 24 hours, and a final urgency message at 72 hours.

// automations/cart-recovery.ts
import cron from 'node-cron';

// Runs every 30 minutes
cron.schedule('*/30 * * * *', async () => {
  const token = await getAccessToken();
  const client = axios.create({
    baseURL: 'https://api.salla.dev/admin/v2',
    headers: { Authorization: `Bearer ${token}` },
  });

  const { data } = await client.get('/carts/abandoned', {
    params: { per_page: 50, sort_by: 'created_at', sort_dir: 'desc' },
  });

  for (const cart of data.data) {
    const hoursAgo = (Date.now() - new Date(cart.created_at).getTime()) / 3_600_000;

    if (hoursAgo >= 1 && hoursAgo < 2 && !cart.recovery_sent_1h) {
      await sendCartReminder(cart, 'reminder');
      await markCartRecoverySent(cart.id, '1h');
    } else if (hoursAgo >= 24 && hoursAgo < 25 && !cart.recovery_sent_24h) {
      const coupon = await createDiscountCoupon(cart.customer.id, 10); // 10% off
      await sendCartReminder(cart, 'discount', coupon.code);
      await markCartRecoverySent(cart.id, '24h');
    } else if (hoursAgo >= 72 && hoursAgo < 73 && !cart.recovery_sent_72h) {
      await sendCartReminder(cart, 'urgency');
      await markCartRecoverySent(cart.id, '72h');
    }
  }
});

Marketing Automation

Salla's coupon API lets you generate unique discount codes programmatically. Combined with customer segments — new customers, high-value returning buyers, customers who haven't ordered in 90 days — you can run sophisticated retention campaigns without a separate marketing tool. The key is to trigger coupons based on behaviour events, not just calendar dates.

// automations/marketing.ts
export async function createPersonalizedCoupon(
  customerId: string,
  type: 'welcome' | 'winback' | 'loyalty',
): Promise {
  const discounts = { welcome: 15, winback: 20, loyalty: 10 };
  const prefixes = { welcome: 'WELCOME', winback: 'BACK', loyalty: 'VIP' };

  const token = await getAccessToken();
  const client = axios.create({
    baseURL: 'https://api.salla.dev/admin/v2',
    headers: { Authorization: `Bearer ${token}` },
  });

  const code = `${prefixes[type]}-${customerId.slice(-6).toUpperCase()}-${Date.now().toString(36).toUpperCase()}`;

  await client.post('/coupons', {
    code,
    discount_type: 'percentage',
    discount_value: discounts[type],
    usage_limit: 1,
    customer_id: customerId,
    expires_at: new Date(Date.now() + 7 * 86400000).toISOString(),
  });

  return code;
}

Connecting Salla to a Telegram Bot

The Telegram bot acts as the nerve center for all these automations. Store owners get real-time notifications, can query data on the go, and can approve flagged orders — all from their phone. Customers get proactive updates without needing to log in to the store. I used this architecture for the IAI project and the owner went from spending 3 hours a day on manual tasks to under 20 minutes.

// bot/index.ts
import { Telegraf } from 'telegraf';

const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN!);

// Owner notifications channel
const OWNER_CHAT_ID = process.env.OWNER_TELEGRAM_ID!;

export async function notifyOwner(message: string, options?: object) {
  await bot.telegram.sendMessage(OWNER_CHAT_ID, message, {
    parse_mode: 'HTML',
    ...options,
  });
}

// Called from order.created webhook
export async function sendNewOrderAlert(order: SallaOrder) {
  const items = order.items.map(i => `• ${i.name} × ${i.quantity}`).join('\n');
  const message =
    `New Order #${order.reference_id}\n` +
    `Customer: ${order.customer.first_name} ${order.customer.last_name}\n` +
    `Total: ${order.amounts.total.amount} ${order.currency}\n\n` +
    `Items:\n${items}`;

  await notifyOwner(message);
}

Building an Analytics Dashboard

The Salla API exposes enough data to build a meaningful analytics layer yourself. I pull daily order totals, top products, and customer acquisition sources into a simple Node.js cron job that stores aggregated metrics in SQLite. Then I serve those metrics as a small Express + Chart.js dashboard. It is not Mixpanel, but for a solo-operator store it covers 90% of the decisions they need to make.

// analytics/daily-snapshot.ts
import Database from 'better-sqlite3';
const db = new Database('analytics.db');

export async function takeDailySnapshot(): Promise {
  const token = await getAccessToken();
  const client = axios.create({
    baseURL: 'https://api.salla.dev/admin/v2',
    headers: { Authorization: `Bearer ${token}` },
  });

  const today = new Date().toISOString().slice(0, 10);

  // Fetch today's orders
  const { data } = await client.get('/orders', {
    params: {
      created_at_min: `${today}T00:00:00Z`,
      created_at_max: `${today}T23:59:59Z`,
      per_page: 200,
    },
  });

  const orders: SallaOrder[] = data.data;
  const totalRevenue = orders.reduce((s, o) => s + parseFloat(o.amounts.total.amount), 0);
  const orderCount = orders.length;
  const avgOrderValue = orderCount ? totalRevenue / orderCount : 0;

  db.prepare(`
    INSERT OR REPLACE INTO daily_metrics (date, revenue, orders, aov)
    VALUES (?, ?, ?, ?)
  `).run(today, totalRevenue, orderCount, avgOrderValue);

  console.log(`[Analytics] ${today}: SAR ${totalRevenue.toFixed(2)} across ${orderCount} orders`);
}

Real Results from the IAI Bot Project

After deploying the full automation suite for the IAI project, here is what changed in the first 30 days:

  • Order processing time dropped from an average of 4 hours to under 3 minutes.
  • Support ticket volume dropped by 61% because customers got instant status updates.
  • Abandoned cart recovery brought in an additional 14% revenue in month one.
  • The store owner saved approximately 25 hours per week of manual work.
  • Zero missed inventory conflicts because the sync ran on every order event.

None of this required a large codebase. The entire automation layer is around 800 lines of TypeScript. The secret is not complexity — it is using Salla's webhooks as the real-time trigger for everything, rather than polling. If you want to discuss building something similar for your store, get in touch here.

Cost Comparison: Manual vs. Automated

A common objection is the upfront development cost. Let me break it down honestly. Hiring someone to handle orders and support 8 hours a day costs roughly SAR 3,000–5,000 per month in Saudi Arabia. A one-time automation build typically runs SAR 4,000–8,000 depending on complexity, plus SAR 50–150 per month for a VPS to run it. The automation pays for itself in the second month and keeps saving money indefinitely — while working 24/7 with no sick days.

Getting Started

The fastest path to a working automation is: (1) register your app in the Salla Partner Portal, (2) set up a Node.js Express server to receive webhooks, (3) start with just the order.created event and a Telegram notification. Once that is working, layer in each automation one at a time. Do not try to build everything at once.

The Salla developer documentation is genuinely good. Their webhook event list is comprehensive and the API reference has live examples. The community on their developer Discord is also active if you get stuck.