Skip to content

    Arabic or English · ↑↓ to move · Enter to open · Esc to close

    العربية
    On this page

    Quickstart: your first message in 3 steps

    Create an API key, send a WhatsApp message from your own number and receive your first signed webhook — in about ten minutes.

    Before you start

    You need a Mursilat account whose plan includes API access, and one number linked in Numbers (scan the QR from WhatsApp → Linked devices). Every request below is real: it uses your number and your plan.

    1. Create an API key

    1. Open Settings → API keys in the dashboard and press New key (account owners only).
    2. Name it after the system that will use it (e.g. “Shop backend”) and pick the Send messages preset — it grants messages:send and messages:read only.
    3. Choose the numbers it may use (all, or only some), and optionally the server IPs allowed to use it.
    4. Copy the key (wsl_live_…). It is shown once; Mursilat keeps only a keyed hash and can never show it again.

    Keep the key in an environment variable on your server, never in a browser or mobile app. Check that it works — GET /v2/me needs no scope and tells you what the key can do:

    curl -X GET "https://mursilat.com/api/v2/me" \
      -H "Authorization: Bearer $WASLA_API_KEY"

    2. Send your first message

    Find the id of your number (a key restricted to some numbers lists only those), then send. Idempotency-Key is required on sends: if your request times out, send it again with the same key and it will never be delivered twice.

    curl -X GET "https://mursilat.com/api/v2/numbers" \
      -H "Authorization: Bearer $WASLA_API_KEY"
    curl -X POST "https://mursilat.com/api/v2/messages" \
      -H "Authorization: Bearer $WASLA_API_KEY" \
      -H "Idempotency-Key: order-1042-confirm" \
      -H "Content-Type: application/json" \
      -d '{
      "numberId": "cm1abcd2345efgh6789ijkl",
      "to": "201001234567",
      "type": "text",
      "text": "طلبك اتشحن 🚚 *شكرًا*",
      "reference": "order-1042"
    }'

    This sends a real WhatsApp message

    Send to your own phone first. Every send goes through the Number Shield: under load you get status: "queued" with estimatedDelayMs, and messages to new contacts are paced.

    3. Receive a webhook

    Delivery receipts, customer replies and button answers arrive at your HTTPS endpoint, signed. Register one URL for all your numbers (this needs a key with webhooks:write — create a second key for it, or add the scope):

    curl -X PUT "https://mursilat.com/api/v2/webhook" \
      -H "Authorization: Bearer $WASLA_API_KEY" \
      -H "Idempotency-Key: order-1042-confirm" \
      -H "Content-Type: application/json" \
      -d '{
      "url": "https://example.com/mursilat",
      "events": null
    }'

    The answer contains the signing secret once. Verify every request with it — compute the HMAC over the raw body, compare in constant time and reject old timestamps:

    // Node.js + Express. Use the RAW body: JSON.parse → JSON.stringify changes bytes and breaks the signature.
    import crypto from 'node:crypto';
    import express from 'express';
    
    const app = express();
    const SECRET = process.env.WASLA_WEBHOOK_SECRET;
    const seen = new Set(); // use Redis/DB in production
    
    function verifyWaslaSignature(rawBody, header, secret, toleranceSec = 300) {
      if (!header || !secret) return false;
      const parts = {};
      for (const pair of header.split(',')) {
        const i = pair.indexOf('=');
        if (i > 0) parts[pair.slice(0, i).trim()] = pair.slice(i + 1).trim();
      }
      const t = parts.t;
      const v1 = parts.v1;
      if (!/^\d+$/.test(t || '') || !/^[0-9a-f]{64}$/i.test(v1 || '')) return false;
      // Reject old (replayed) or far-future timestamps.
      if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > toleranceSec) return false;
      const expected = crypto
        .createHmac('sha256', secret)
        .update(t + '.')
        .update(rawBody) // the RAW bytes exactly as received, not re-serialized JSON
        .digest();
      const received = Buffer.from(v1, 'hex');
      return received.length === expected.length && crypto.timingSafeEqual(received, expected);
    }
    
    app.post('/webhooks/mursilat', express.raw({ type: 'application/json' }), (req, res) => {
      if (!verifyWaslaSignature(req.body, req.get('X-Wasla-Signature'), SECRET)) {
        return res.status(401).send('invalid signature');
      }
      const event = JSON.parse(req.body.toString('utf8'));
      res.sendStatus(200); // answer fast (within 10s); do the work after
    
      if (seen.has(event.id)) return; // retries reuse the same event id
      seen.add(event.id);
      if (event.type === 'interactive.reply') {
        console.log(event.data.reference, '→', event.data.reply.id);
      }
    });
    
    app.listen(3000);

    Then trigger a test event and watch it arrive (and in the delivery log):

    curl -X POST "https://mursilat.com/api/v2/webhook/test" \
      -H "Authorization: Bearer $WASLA_API_KEY" \
      -H "Idempotency-Key: order-1042-confirm" \
      -H "Content-Type: application/json" \
      -d '{
      "numberId": "cm1abcd2345efgh6789ijkl"
    }'

    You can paste what your server received into the payload inspector to check the signature in your browser.

    Next steps