Send a voice note

POSThttps://api.wapito.com/v1/messages/voice

Send a channel token on every request: Authorization: Bearer wpt_YOUR_TOKEN

Sends a voice note: the waveform bubble WhatsApp shows for messages recorded in the app, complete with a played receipt once the recipient listens. Wapito converts your input to the Opus in OGG format WhatsApp requires, so MP3, WAV and M4A all work. Voice notes feel personal and get read, which also makes them easy to overuse in outreach.

Request body

Fields of the request body
FieldTypeRequiredDescription
captionstringOptionalCaption rendered under the media. Ignored for stickers, audio and voice notes.
filenamestringOptionalOverride the file name the recipient sees.
mediastring | objectRequiredWhere the file comes from. Pass a string to use an HTTPS URL, a `data:` URI or a Wapito media id returned by `POST /media`, or pass a `MediaInput` object when you need to override the media type or file name.
datastringOptionalBase64-encoded file bytes, without a data URL prefix.
filenamestringOptionalFile name shown to the recipient.
mimetypestringOptionalIANA media type; required with `data`, inferred from the response headers with `url`.
urlstringOptionalPublicly reachable HTTPS URL Wapito downloads before sending.uri
mentionsarray of stringOptionalPhone numbers in digits to mention in the caption.
quotedstringOptionalId of a message in the same chat to reply to.
tostringRequiredA WhatsApp recipient. Accepted forms: `+E.164` (`+15551234567`), bare digits (`15551234567`), `<digits>@s.whatsapp.net`, `<digits>@c.us`, `<group id>@g.us`, `<lid>@lid` and `<channel id>@newsletter`. Phone forms are validated with libphonenumber and rejected with `invalid_recipient` when they are not dialable. Wapito normalises the value before it reaches the engine and always returns `@s.whatsapp.net` ids.
typing_timeintegerOptionalSeconds of simulated typing before the message is sent, 0-15.0–15
{
  "media": "https://acme.example/audio/reply-4182.m4a",
  "to": "+15551234567"
}

Responses

201

The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later as `messages.status` events.

Fields of the 201 response
FieldTypeRequiredDescription
audioobjectOptionalMedia attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL.
captionstring | nullRequiredCaption sent alongside the media.
errorstringOptionalSet to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable
file_namestring | nullRequiredOriginal file name when the sender supplied one.
file_sizeinteger | nullRequiredSize in bytes.
idstring | nullRequiredWapito media id (`med_` + ULID), or `null` when the copy from the engine failed.
linkstring | nullRequiredSigned `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium.
mime_typestringRequiredIANA media type reported by WhatsApp.
chat_idstringRequiredChat the message belongs to, as `@s.whatsapp.net`, `@g.us` or `@newsletter`.
contactobjectOptionalContact card payload of a `contact` message.
vcardstringRequiredRaw vCard 3.0 text as WhatsApp stores it.
contextobjectOptionalReply, forward and mention context for a message.
forwardedbooleanRequiredWhether WhatsApp marked the message as forwarded.
mentionsarray of stringRequiredPhone numbers mentioned with @ in the body.
quoted_authorstring | nullRequiredSender of the quoted message.
quoted_idstring | nullRequiredId of the quoted message, when this message is a reply.
documentobjectOptionalMedia attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL.
captionstring | nullRequiredCaption sent alongside the media.
errorstringOptionalSet to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable
file_namestring | nullRequiredOriginal file name when the sender supplied one.
file_sizeinteger | nullRequiredSize in bytes.
idstring | nullRequiredWapito media id (`med_` + ULID), or `null` when the copy from the engine failed.
linkstring | nullRequiredSigned `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium.
mime_typestringRequiredIANA media type reported by WhatsApp.
fromstring | nullRequiredSender in digits for direct chats, or the group id for group chats. `null` when only a LID is known.
from_lidstring | nullRequiredSender LID when WhatsApp addressed the message by LID rather than phone number.
from_mebooleanRequiredTrue when the channel sent the message, false when it received it.
from_namestring | nullRequiredPush name of the sender as WhatsApp reports it.
idstringRequiredEngine message id, unique within the channel.
imageobjectOptionalMedia attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL.
captionstring | nullRequiredCaption sent alongside the media.
errorstringOptionalSet to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable
file_namestring | nullRequiredOriginal file name when the sender supplied one.
file_sizeinteger | nullRequiredSize in bytes.
idstring | nullRequiredWapito media id (`med_` + ULID), or `null` when the copy from the engine failed.
linkstring | nullRequiredSigned `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium.
mime_typestringRequiredIANA media type reported by WhatsApp.
locationobjectOptionalLocation payload of a `location` message.
addressstring | nullRequiredStreet address shown under the place name.
latitudenumberRequiredDecimal degrees.
longitudenumberRequiredDecimal degrees.
namestring | nullRequiredPlace name shown in the bubble.
participantstring | nullRequiredIn group chats, the individual sender behind `from`.
pollobjectOptionalPoll payload of a `poll` message. Votes arrive later as `polls` webhook events.
multiplebooleanRequiredWhether voters may select more than one option.
optionsarray of stringRequiredAnswer options in display order.
titlestringRequiredPoll question.
rawobjectOptionalUntouched engine payload. Present only while `settings.include_raw` is enabled.
sourcestringRequired`app` when the message came from the linked phone, `api` when Wapito sent it.one of app, api
statusstringOptionalDelivery state of an outbound message.one of failed, pending, sent, delivered, read, played
stickerobjectOptionalMedia attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL.
captionstring | nullRequiredCaption sent alongside the media.
errorstringOptionalSet to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable
file_namestring | nullRequiredOriginal file name when the sender supplied one.
file_sizeinteger | nullRequiredSize in bytes.
idstring | nullRequiredWapito media id (`med_` + ULID), or `null` when the copy from the engine failed.
linkstring | nullRequiredSigned `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium.
mime_typestringRequiredIANA media type reported by WhatsApp.
textobjectOptionalBody of a `text` message.
bodystringRequiredMessage text, WhatsApp markdown allowed.
timestampintegerRequiredUnix epoch milliseconds.
typestringRequiredMessage kind; selects which payload field is populated.one of text, image, video, audio, voice, document, sticker, location, contact, poll, reaction, unknown
videoobjectOptionalMedia attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL.
captionstring | nullRequiredCaption sent alongside the media.
errorstringOptionalSet to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable
file_namestring | nullRequiredOriginal file name when the sender supplied one.
file_sizeinteger | nullRequiredSize in bytes.
idstring | nullRequiredWapito media id (`med_` + ULID), or `null` when the copy from the engine failed.
linkstring | nullRequiredSigned `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium.
mime_typestringRequiredIANA media type reported by WhatsApp.
voiceobjectOptionalMedia attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL.
captionstring | nullRequiredCaption sent alongside the media.
errorstringOptionalSet to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable
file_namestring | nullRequiredOriginal file name when the sender supplied one.
file_sizeinteger | nullRequiredSize in bytes.
idstring | nullRequiredWapito media id (`med_` + ULID), or `null` when the copy from the engine failed.
linkstring | nullRequiredSigned `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium.
mime_typestringRequiredIANA media type reported by WhatsApp.
{
  "chat_id": "15551234567@s.whatsapp.net",
  "context": {
    "forwarded": false,
    "mentions": [],
    "quoted_author": null,
    "quoted_id": null
  },
  "from": "15557654321",
  "from_lid": null,
  "from_me": true,
  "from_name": "Acme Support",
  "id": "true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B",
  "participant": null,
  "source": "api",
  "status": "sent",
  "timestamp": 1789459200000,
  "type": "voice",
  "voice": {
    "caption": null,
    "file_name": null,
    "file_size": 84221,
    "id": "med_01JRQ9FFG7H8J9K0L1M2N3P4Q5",
    "link": "https://api.wapito.com/v1/media/med_01JRQ9FFG7H8J9K0L1M2N3P4Q5?exp=1790064500&sig=6c04e83b15af729d",
    "mime_type": "audio/ogg; codecs=opus"
  }
}

400

The request body or query string is malformed, or the recipient cannot be parsed into a WhatsApp id.

Show 2 example bodies

invalid_recipient — `to` is not a dialable number or valid WhatsApp id

{
  "error": {
    "code": "invalid_recipient",
    "details": {
      "to": "+1555"
    },
    "message": "The recipient is not a valid WhatsApp address.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

invalid_request — schema validation failed

{
  "error": {
    "code": "invalid_request",
    "details": {
      "issues": [
        {
          "message": "Array must contain at least 2 element(s)",
          "path": "body.options"
        }
      ]
    },
    "message": "The request payload failed validation.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

401

The channel token is missing, malformed, revoked or belongs to a deleted channel.

Show 2 example bodies

token_revoked — the token was rotated in the dashboard

{
  "error": {
    "code": "token_revoked",
    "message": "This channel token has been revoked.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

unauthorized — no or unusable Bearer token

{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid channel token.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

403

The token is valid but the channel may not perform this action right now.

Show 2 example bodies

channel_locked — billing lapsed or the channel was locked by an operator

{
  "error": {
    "code": "channel_locked",
    "details": {
      "reason": "plan_required"
    },
    "message": "This channel is locked.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

forbidden — the channel does not own the target object

{
  "error": {
    "code": "forbidden",
    "message": "You are not allowed to perform this action.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

409

The channel is in the wrong state for this action, or the account type does not support it.

Show 3 example bodies

business_account_required — labels need WhatsApp Business

{
  "error": {
    "code": "business_account_required",
    "message": "This action requires a WhatsApp Business account.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

channel_not_connected — link the number first

{
  "error": {
    "code": "channel_not_connected",
    "details": {
      "status": "qr"
    },
    "message": "The channel is not connected.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

channel_not_in_qr_state — no QR available while the session boots

{
  "error": {
    "code": "channel_not_in_qr_state",
    "details": {
      "status": "created"
    },
    "message": "The channel is not waiting for a QR scan.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

413

The upload exceeds the media size cap for the plan.

Show the example body

payload_too_large — 16 MB on sandbox, 64 MB on premium

{
  "error": {
    "code": "payload_too_large",
    "details": {
      "max_bytes": 16777216
    },
    "message": "The request payload is too large.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

415

The file type cannot be sent as the requested message kind.

Show the example body

unsupported_media_type — wrong media type for the endpoint

{
  "error": {
    "code": "unsupported_media_type",
    "details": {
      "mimetype": "application/x-msdownload"
    },
    "message": "This media type is not supported.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

423

WhatsApp has temporarily blocked outbound first contact from this number.

Show the example body

reachout_timelocked — wait until `details.until`

{
  "error": {
    "code": "reachout_timelocked",
    "details": {
      "until": "2026-09-15T14:00:00.000Z"
    },
    "message": "WhatsApp has temporarily locked reach-outs for this channel.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

429

A rate limit, a plan quota or one of the anti-ban guards stopped the request. Every one of these is safe to retry later; read `Retry-After` when present.

Show 5 example bodies

cold_send_limit — too many first messages to new recipients this hour

{
  "error": {
    "code": "cold_send_limit",
    "details": {
      "resets_at": "2026-09-15T09:00:00.000Z",
      "window_cap": 20
    },
    "message": "The hourly limit for messages to new recipients is reached.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

quota_exceeded — plan quota for the day or month

{
  "error": {
    "code": "quota_exceeded",
    "details": {
      "quota": "sent",
      "resets_at": "2026-09-16T00:00:00.000Z",
      "used": 150
    },
    "message": "The plan quota for this resource is exhausted.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

rate_limited — per-minute API rate limit

{
  "error": {
    "code": "rate_limited",
    "details": {
      "retry_after": 12
    },
    "message": "Too many requests.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

send_rate_limited — the send queue did not drain within 20 s

{
  "error": {
    "code": "send_rate_limited",
    "details": {
      "retry_after": 5
    },
    "message": "The send queue for this channel is saturated.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

warmup_limit — the warm-up ladder cap for today

{
  "error": {
    "code": "warmup_limit",
    "details": {
      "cap": 200,
      "day": 2
    },
    "message": "The warm-up limit for this channel is reached.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

500

Something went wrong inside Wapito. Quote `request_id` when reporting it.

Show the example body

internal_error — unexpected failure

{
  "error": {
    "code": "internal_error",
    "message": "Something went wrong on our side.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

502

The engine answered with an error Wapito could not translate into a more specific code.

Show the example body

engine_error — unexpected engine failure

{
  "error": {
    "code": "engine_error",
    "details": {
      "engine": "gows",
      "status": 500
    },
    "message": "The WhatsApp engine returned an error.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

503

The engine session is not reachable. Retry with backoff.

Show the example body

engine_unavailable — engine down or restarting

{
  "error": {
    "code": "engine_unavailable",
    "details": {
      "engine": "gows"
    },
    "message": "The WhatsApp engine is unavailable.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

504

The engine did not answer before the upstream timeout.

Show the example body

engine_timeout — no answer within the deadline

{
  "error": {
    "code": "engine_timeout",
    "details": {
      "timeout_ms": 20000
    },
    "message": "The WhatsApp engine did not respond in time.",
    "request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
  }
}

Webhook events

Errors

Code examples

import requests

url = "https://api.wapito.com/v1/messages/voice"

payload = {
    "to": "+15551234567",
    "media": "https://acme.example/audio/reply-4182.m4a"
}
headers = {
    "Authorization": "Bearer wpt_YOUR_TOKEN",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())

Used in

Related

Try it on your own number

Create a channel, link a WhatsApp number by QR or pairing code, and call the API in a couple of minutes. The Sandbox plan is free and needs no card.