Forward a message to another chat
POSThttps://api.wapito.com/v1/messages/{id}/forward
Send a channel token on every request: Authorization: Bearer wpt_YOUR_TOKEN
Forwards an existing message to another chat, keeping the original media without re-uploading it and marking the result as forwarded for the recipient. The default GOWS engine cannot forward, so a channel on GOWS gets `engine_unsupported_feature` with a hint to switch; NOWEB channels forward normally. A forward counts against the send quota exactly like a new message.
Not on every engine. A channel runs on one of two protocol engines, gows (the default) or noweb, and this call is not implemented on both. On the engine that lacks it the API answers 501 engine_unsupported_feature with a hint naming the engine that has it.
Parameters
| Name | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
id | path | string | Required | Message id as returned by a send call or delivered in a `messages` webhook event. | true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Required | A 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_time | integer | Optional | Seconds of simulated typing before the forward is sent, 0-15.0–15 |
{
"to": "120363041234567890@g.us"
}Responses
201
The message was accepted and queued on the per-channel send queue. Delivery receipts arrive later as `messages.status` events.
| Field | Type | Required | Description |
|---|---|---|---|
audio | object | Optional | Media attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL. |
caption | string | null | Required | Caption sent alongside the media. |
error | string | Optional | Set to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable |
file_name | string | null | Required | Original file name when the sender supplied one. |
file_size | integer | null | Required | Size in bytes. |
id | string | null | Required | Wapito media id (`med_` + ULID), or `null` when the copy from the engine failed. |
link | string | null | Required | Signed `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium. |
mime_type | string | Required | IANA media type reported by WhatsApp. |
chat_id | string | Required | Chat the message belongs to, as `@s.whatsapp.net`, `@g.us` or `@newsletter`. |
contact | object | Optional | Contact card payload of a `contact` message. |
vcard | string | Required | Raw vCard 3.0 text as WhatsApp stores it. |
context | object | Optional | Reply, forward and mention context for a message. |
forwarded | boolean | Required | Whether WhatsApp marked the message as forwarded. |
mentions | array of string | Required | Phone numbers mentioned with @ in the body. |
quoted_author | string | null | Required | Sender of the quoted message. |
quoted_id | string | null | Required | Id of the quoted message, when this message is a reply. |
document | object | Optional | Media attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL. |
caption | string | null | Required | Caption sent alongside the media. |
error | string | Optional | Set to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable |
file_name | string | null | Required | Original file name when the sender supplied one. |
file_size | integer | null | Required | Size in bytes. |
id | string | null | Required | Wapito media id (`med_` + ULID), or `null` when the copy from the engine failed. |
link | string | null | Required | Signed `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium. |
mime_type | string | Required | IANA media type reported by WhatsApp. |
from | string | null | Required | Sender in digits for direct chats, or the group id for group chats. `null` when only a LID is known. |
from_lid | string | null | Required | Sender LID when WhatsApp addressed the message by LID rather than phone number. |
from_me | boolean | Required | True when the channel sent the message, false when it received it. |
from_name | string | null | Required | Push name of the sender as WhatsApp reports it. |
id | string | Required | Engine message id, unique within the channel. |
image | object | Optional | Media attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL. |
caption | string | null | Required | Caption sent alongside the media. |
error | string | Optional | Set to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable |
file_name | string | null | Required | Original file name when the sender supplied one. |
file_size | integer | null | Required | Size in bytes. |
id | string | null | Required | Wapito media id (`med_` + ULID), or `null` when the copy from the engine failed. |
link | string | null | Required | Signed `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium. |
mime_type | string | Required | IANA media type reported by WhatsApp. |
location | object | Optional | Location payload of a `location` message. |
address | string | null | Required | Street address shown under the place name. |
latitude | number | Required | Decimal degrees. |
longitude | number | Required | Decimal degrees. |
name | string | null | Required | Place name shown in the bubble. |
participant | string | null | Required | In group chats, the individual sender behind `from`. |
poll | object | Optional | Poll payload of a `poll` message. Votes arrive later as `polls` webhook events. |
multiple | boolean | Required | Whether voters may select more than one option. |
options | array of string | Required | Answer options in display order. |
title | string | Required | Poll question. |
raw | object | Optional | Untouched engine payload. Present only while `settings.include_raw` is enabled. |
source | string | Required | `app` when the message came from the linked phone, `api` when Wapito sent it.one of app, api |
status | string | Optional | Delivery state of an outbound message.one of failed, pending, sent, delivered, read, played |
sticker | object | Optional | Media attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL. |
caption | string | null | Required | Caption sent alongside the media. |
error | string | Optional | Set to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable |
file_name | string | null | Required | Original file name when the sender supplied one. |
file_size | integer | null | Required | Size in bytes. |
id | string | null | Required | Wapito media id (`med_` + ULID), or `null` when the copy from the engine failed. |
link | string | null | Required | Signed `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium. |
mime_type | string | Required | IANA media type reported by WhatsApp. |
text | object | Optional | Body of a `text` message. |
body | string | Required | Message text, WhatsApp markdown allowed. |
timestamp | integer | Required | Unix epoch milliseconds. |
type | string | Required | Message kind; selects which payload field is populated.one of text, image, video, audio, voice, document, sticker, location, contact, poll, reaction, unknown |
video | object | Optional | Media attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL. |
caption | string | null | Required | Caption sent alongside the media. |
error | string | Optional | Set to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable |
file_name | string | null | Required | Original file name when the sender supplied one. |
file_size | integer | null | Required | Size in bytes. |
id | string | null | Required | Wapito media id (`med_` + ULID), or `null` when the copy from the engine failed. |
link | string | null | Required | Signed `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium. |
mime_type | string | Required | IANA media type reported by WhatsApp. |
voice | object | Optional | Media attached to a message. `link` is a signed Wapito URL, not a WhatsApp CDN URL. |
caption | string | null | Required | Caption sent alongside the media. |
error | string | Optional | Set to `unavailable` when the media could not be copied before it expired on the engine.one of unavailable |
file_name | string | null | Required | Original file name when the sender supplied one. |
file_size | integer | null | Required | Size in bytes. |
id | string | null | Required | Wapito media id (`med_` + ULID), or `null` when the copy from the engine failed. |
link | string | null | Required | Signed `/v1/media/{id}?exp&sig` URL, valid 24 h on sandbox and 7 days on premium. |
mime_type | string | Required | IANA media type reported by WhatsApp. |
{
"chat_id": "120363041234567890@g.us",
"context": {
"forwarded": true,
"mentions": [],
"quoted_author": null,
"quoted_id": null
},
"from": "120363041234567890@g.us",
"from_lid": null,
"from_me": true,
"from_name": "Acme Support",
"id": "true_120363041234567890@g.us_5C71E90AB3F4D26178BB",
"participant": "15557654321",
"source": "api",
"status": "sent",
"text": {
"body": "Standup starts in 10 minutes @15551234567"
},
"timestamp": 1789459410000,
"type": "text"
}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"
}
}404
The addressed object does not exist, or the number is not on WhatsApp.
Show 3 example bodies
message_not_found — the message id is unknown to the engine
{
"error": {
"code": "message_not_found",
"message": "The requested message does not exist.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}not_found — unknown id
{
"error": {
"code": "not_found",
"message": "The requested resource does not exist.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}not_on_whatsapp — the number is not registered
{
"error": {
"code": "not_on_whatsapp",
"message": "This number is not registered on WhatsApp.",
"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"
}
}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"
}
}501
The feature is not available. `engine_unsupported_feature` means the other engine can do it and `details.hint` tells you how to switch; `feature_not_available` means no engine supports it yet.
Show 2 example bodies
engine_unsupported_feature — supported on the other engine
{
"error": {
"code": "engine_unsupported_feature",
"details": {
"hint": "Switch this channel to the noweb engine in the dashboard.",
"supported_engines": [
"noweb"
]
},
"message": "The engine of this channel does not support that feature.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}feature_not_available — phase 2, no engine support
{
"error": {
"code": "feature_not_available",
"details": {
"phase": 2
},
"message": "This feature is not available.",
"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
- messagesMessage sent or received
onMessages() - messages.statusDelivery receipt for a sent message
onMessagesStatus()
Errors
Code examples
import requests
url = "https://api.wapito.com/v1/messages/true_15551234567@s.whatsapp.net_3EB0C767D82B0A1E4F2B/forward"
payload = { "to": "120363041234567890@g.us" }
headers = {
"Authorization": "Bearer wpt_YOUR_TOKEN",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())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.