Messages
Schedule a message
/v2/messages- Scope:
messages:send - Idempotency-Key:required
- available on request
- sends messages
available on request
Scheduling is available on request and is off by default: until it is enabled for you, a request with scheduleAt answers 422 SCHEDULING_DISABLED. A scheduled message is the only message content Mursilat keeps at rest (encrypted, at most 30 days), so it stays off unless you ask support to enable it. Sending now (without scheduleAt) always works.
Available on request: scheduling is off by default (422 SCHEDULING_DISABLED) because a scheduled message is the only content Mursilat keeps at rest — encrypted, at most 30 days, deleted once it leaves. Ask support to enable it. Cancel with DELETE /v2/messages/{id}.
Parameters
Headers
| Name | Type | Constraints | Description |
|---|---|---|---|
Idempotency-Keyrequired | Typestring | Constraints≤ 191 chars | DescriptionYour unique id for this operation (≤ 191 visible ASCII). A retry with the same key and body returns the first answer with Idempotent-Replayed: true — it never runs twice. Kept 24 h. |
X-Request-Idoptional | Typestring | Constraints— | DescriptionOptional: your own request id, echoed back (otherwise we generate req_…). Quote it to support. |
Body
| Name | Type | Constraints | Description |
|---|---|---|---|
numberIdrequired | Typestring | Constraints— | DescriptionNumber to act on (id from GET /v2/numbers). A key restricted to some numbers can use only those. |
torequired | Typestring | Constraints— | DescriptionRecipient: international digits without + or leading zeros (e.g. 201001234567, 966501234567). |
typerequired | Typestring | Constraints— | DescriptionMessage type: text, image, video, audio, voice, document, sticker, location, contact, poll, event, buttons, list, cta_url. Each type has its own fields — see the table below. |
textoptional | Typestring | Constraints— | DescriptionMessage text. WhatsApp formatting passes through (*bold*, _italic_, ~strike~). |
referenceoptional | Typestring | Constraints≤ 128 chars | DescriptionYour own id (order number, ticket…). Echoed in message.status / interactive.reply webhooks and filterable in GET /v2/messages. |
scheduleAtoptional | Typestring (date-time) | Constraints— | DescriptionISO 8601 time 1 minute – 30 days ahead. Available on request (otherwise 422 SCHEDULING_DISABLED). |
Message types
type | Fields | Notes |
|---|---|---|
text | Fieldstext (1–4096), linkPreview | NotesWhatsApp formatting passes through. A preview of the first https link is built safely when possible. |
image | Fieldsmedia, caption ≤ 1024, viewOnce | NotesJPEG · PNG · WebP, ≤ 5 MB. |
video | Fieldsmedia, caption, viewOnce, gif, videoNote | NotesMP4 · 3GPP, ≤ 16 MB. videoNote: round, ≤ 60 s, no caption. |
audio | Fieldsmedia | NotesOGG · MP3 · M4A · AAC · AMR, ≤ 16 MB. Shown as an audio file. |
voice | Fieldsmedia, viewOnce | NotesShown as a voice note (OGG/Opus keeps the waveform). |
document | Fieldsmedia, fileName ≤ 120, caption | NotesAny file except programs, ≤ 100 MB. |
sticker | Fieldsmedia | NotesWebP ≤ 500 KB as is; PNG / JPG / GIF ≤ 5 MB are converted. |
location | Fieldslatitude, longitude, name ≤ 100, address ≤ 200 | NotesA static pin (live location isn’t possible from a linked number). |
contact | Fieldscontacts[] 1–5: name, phones[] 1–3, email, organization | NotesThe vCard is built on the server. |
poll | Fieldsname ≤ 255, options[] 2–12, selectableCount | NotesVotes arrive as poll.vote webhooks. |
event | Fieldsname, startAt, endAt, description, locationName | NotesExperimental: some phones show 1:1 events as unsupported. |
buttons | Fieldsbody.text, buttons[] 1–3, header, footer, fallback | NotesNumbered fallback by default; answers arrive as interactive.reply. |
list | Fieldsbody.text, list.buttonText, list.sections[] (≤ 10 rows) | NotesA menu button that opens the rows. |
cta_url | Fieldsbody.text, cta.title ≤ 20, cta.url | NotesOne link button; link rules apply to the URL. |
Request
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": "تذكير بموعدك بكرة الساعة 5",
"scheduleAt": "2026-10-14T13:00:00+02:00"
}'const res = await fetch('https://mursilat.com/api/v2/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WASLA_API_KEY}`,
'Idempotency-Key': 'order-1042-confirm',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"numberId": "cm1abcd2345efgh6789ijkl",
"to": "201001234567",
"type": "text",
"text": "تذكير بموعدك بكرة الساعة 5",
"scheduleAt": "2026-10-14T13:00:00+02:00"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data);import os
import requests
headers = {"Authorization": f"Bearer {os.environ['WASLA_API_KEY']}", "Idempotency-Key": "order-1042-confirm"}
res = requests.post(
"https://mursilat.com/api/v2/messages",
headers=headers,
json={
"numberId": "cm1abcd2345efgh6789ijkl",
"to": "201001234567",
"type": "text",
"text": "تذكير بموعدك بكرة الساعة 5",
"scheduleAt": "2026-10-14T13:00:00+02:00"
},
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data)<?php
$ch = curl_init('https://mursilat.com/api/v2/messages');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: order-1042-confirm',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'numberId' => 'cm1abcd2345efgh6789ijkl',
'to' => '201001234567',
'type' => 'text',
'text' => 'تذكير بموعدك بكرة الساعة 5',
'scheduleAt' => '2026-10-14T13:00:00+02:00'
], JSON_UNESCAPED_UNICODE),
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) { throw new Exception($data['error']['code'] . ': ' . $data['error']['message']); }
print_r($data);{
"numberId": "cm1abcd2345efgh6789ijkl",
"to": "201001234567",
"type": "text",
"text": "تذكير بموعدك بكرة الساعة 5",
"scheduleAt": "2026-10-14T13:00:00+02:00"
}Response
200Example
{
"id": "3EB0A1B2C3D4E5F60718",
"numberId": "cm1abcd2345efgh6789ijkl",
"status": "scheduled",
"type": "text",
"reference": "order-1042",
"scheduledAt": "2026-10-14T11:00:00.000Z",
"interactionId": null,
"format": null,
"fallbackIncluded": null,
"estimatedDelayMs": null
}Response schema
Fields of the example answer. Expand objects and arrays.
responseobject10 fields
- idstring "3EB0A1B2C3D4E5F60718"
- numberIdstring "cm1abcd2345efgh6789ijkl"
- statusstring "scheduled"
- typestring "text"
- referencestring "order-1042"
- scheduledAtstring "2026-10-14T11:00:00.000Z"
- interactionIdnull null
- formatnull null
- fallbackIncludednull null
- estimatedDelayMsnull null
Errors
Besides the authentication, rate-limit and validation errors every endpoint can return:
- 422SCHEDULING_DISABLEDScheduled messages are not enabled (available on request).
- 400SCHEDULE_AT_INVALIDscheduleAt must be 1 minute – 30 days ahead (ISO 8601).
- 422SCHEDULE_MEDIA_URL_ONLYScheduled media must use media.url.
Number Shield & behaviour
At send time the Number Shield applies as usual. Retryable refusals (pacing, caps, quiet hours, a paused or disconnected number) reschedule for up to 24 h; others fail with a message.status: failed webhook.
Try it
Send this request from your browser to the Mursilat API.
documented examplePreview — this copy of the docs isn’t on mursilat.com, so the console shows the documented example instead of calling the API.
Kept only in this tab’s memory: never saved, never put in the URL, sent only to mursilat.com. Reloading the page forgets it.