Guides

Libraries

There is no SDK to install — the API is plain REST over HTTPS. Copy one of these tiny clients into your project and you have typed, reusable access in a few lines.

One dependency, at most

The Node and Python clients use the runtime’s built-in HTTP (Node 18+ fetch) or the ubiquitous requests package. PHP uses cURL from the standard library. Keep your key in an environment variable, never in code.

A reusable client

Each client centralizes the base URL, auth header, and error handling so your calls stay one-liners.

// mywa.js — works on Node 18+ (built-in fetch), Bun, Deno
const BASE = process.env.MYWA_API ?? 'https://api.mywa.net/api/public/v1';
const KEY = process.env.MYWA_KEY;

async function request(method, path, body) {
  const res = await fetch(BASE + path, {
    method,
    headers: {
      Authorization: `Bearer ${KEY}`,
      'Content-Type': 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
  });

  const data = await res.json().catch(() => ({}));
  if (!res.ok) {
    const err = data.error ?? {};
    throw new Error(`MYWA ${res.status} ${err.code ?? ''}: ${err.message ?? res.statusText}`);
  }
  return data;
}

export const mywa = {
  get: (path) => request('GET', path),
  post: (path, body) => request('POST', path, body),
};

Using it

Once the client exists, every endpoint is a one-liner. Here’s sending an OTP and then checking its delivery status.

import { mywa } from './mywa.js';

// Send an OTP
const msg = await mywa.post('/messages', {
  to: '+15551234567',
  type: 'template',
  template: { name: 'otp_code', variables: ['483920'] },
});
console.log('sent', msg.id);

// Later: check status
const status = await mywa.get(`/messages/${msg.id}`);
console.log(status.status); // "delivered"

Starting automations

List the automations you can start, start one with vars its steps can use, then check the run. See Automations for every field.

import { mywa } from './mywa.js';

// What can I start?
const { automations } = await mywa.get('/automations');
const followUp = automations.find((a) => a.name === 'Order follow-up');

// Start it — steps can use {{vars.hook.orderId}}
const { runId } = await mywa.post(`/automations/${followUp.id}/enroll`, {
  phone: '+15551234567',
  vars: { orderId: 'A-1049', amount: 49.99 },
});

// Later: how did it go?
const run = await mywa.get(`/automation-runs/${runId}`);
console.log(run.status, run.goal?.name ?? 'no goal yet');

Retry with backoff

Wrap sends so 429 and 5xx retry automatically. Never retry 4xx other than 429.

async function withRetry(fn, tries = 4) {
  for (let i = 0; i < tries; i++) {
    try {
      return await fn();
    } catch (e) {
      const status = Number(String(e.message).match(/MYWA (\d+)/)?.[1]);
      const retryable = status === 429 || status >= 500;
      if (!retryable || i === tries - 1) throw e;
      await new Promise((r) => setTimeout(r, 2 ** i * 500));
    }
  }
}

await withRetry(() =>
  mywa.post('/messages', { to, type: 'template', template }),
);

Ready for real flows?

The Use cases guide builds complete OTP, abandoned-cart, order-update, and automation flows on top of these clients.
MywaHey 👋 got a question? Say hi, a real human replies here.now