UPPU API documentation

Getting started with the UPPU API

The UPPU API v1 lets your own systems send and receive WhatsApp, Messenger, Instagram, Telegram and web chat messages through UPPU. Your code talks only to UPPU: UPPU talks to the messaging networks, keeps the reply window and consent rules, and sends every event to you as a signed webhook.

This guide takes you from the first key to production. The API reference lists every endpoint and field, the webhooks page every event, and the code examples show the common tasks in PHP, Node.js, Python and C#.

Environments

Production Staging
Base URL https://messaging.uppu.net/v1 https://messaging-staging.uppu.net/v1
Key prefix uppu_live_ uppu_test_
Simulated WhatsApp channel no yes

Before you start

  1. Your company's plan includes the API. UPPU sets the plan. Without the API every request gets 403 api_disabled with details.reason not_in_plan, and no events are sent. Ask your UPPU contact.
  2. A channel is connected (WhatsApp, Messenger, Instagram, Telegram or web chat). On a channel that your own system answers, the company owner sets the channel's Handling to External system (API) on the channels page: the team inbox then does not auto-assign its conversations, and agents can still read and reply.
  3. The company owner creates a key in the UPPU panel: the company's settings, then Developers. Only the owner manages keys and webhooks, after confirming the password.

API keys

Scopes

A key carries scopes (the panel calls them permissions). The panel offers three presets; "custom" picks scopes one by one. A missing scope gets 403 insufficient_scope with details.required.

ScopeAllowsEndpointsBotNotification senderRead only
Every keyRead the channels of the keyGET /v1/channels
GET /v1/channels/{id}
yesyesyes
messages:sendSend messages, read receipts and typing indicatorsPOST /v1/conversations/{id}/typing
POST /v1/messages
POST /v1/messages/{id}/read
yesyes—
messages:readRead messages and conversationsGET /v1/conversations
GET /v1/conversations/{id}
GET /v1/messages
GET /v1/messages/{id}
yesyesyes
conversations:writeResolve and hand over conversationsPOST /v1/conversations/{id}/handover
POST /v1/conversations/{id}/release
POST /v1/conversations/{id}/resolve
yes——
media:writeUpload mediaPOST /v1/mediayesyes—
media:readDownload mediaGET /v1/media/{id}
GET /v1/media/{id}/content
POST /v1/media/{id}/link
yes—yes
templates:readRead templatesGET /v1/templates
GET /v1/templates/{id}
yesyesyes
contacts:readLook up and read contactsGET /v1/contacts/{id}
GET /v1/contacts/{id}/consents
POST /v1/contacts/lookup
yesyesyes
consents:writeRecord consentsPOST /v1/consents—yes—
events:readRead eventsGET /v1/events—yesyes
webhooks:manageManage webhook endpointsDELETE /v1/webhook-endpoints/{id}
GET /v1/webhook-endpoints
GET /v1/webhook-endpoints/{id}
PATCH /v1/webhook-endpoints/{id}
POST /v1/webhook-endpoints
POST /v1/webhook-endpoints/{id}/activate
POST /v1/webhook-endpoints/{id}/disable
POST /v1/webhook-endpoints/{id}/probe
POST /v1/webhook-endpoints/{id}/secret
POST /v1/webhook-endpoints/{id}/test
———
reports:readRead reportsGET /v1/reports/daily
GET /v1/reports/summary
———
templates:writeCreate and delete WhatsApp templatesDELETE /v1/templates/{id}
POST /v1/templates
———

The simulator endpoints (/v1/sandbox/...) exist only on staging and need messages:send; /v1/sandbox/action-target needs no key (see the staging simulator below).

The staging simulator

On staging the company owner can add up to three simulated WhatsApp channels on the Developers page. A simulated channel behaves like a real WhatsApp channel through the API and the webhooks (reply window, consent rules, idempotency, statuses, media, events), but nothing reaches Meta: statuses arrive a few seconds apart as message.status events. Create a test key bound to the simulated channel with the Bot preset.

Magic recipients. The last two digits of the contact's phone number choose what happens to a message you send:

Last two digits Result
any other accepted, sent, delivered, read
01 accepted, sent, delivered (never read)
02 accepted, sent (no delivery report)
03 accepted, then failed with recipient_unreachable
04 accepted, then failed with window_closed
05 accepted, then failed with marketing_opt_out (UPPU records the withdrawal)
06 accepted, then failed with payment_required
07 refused at once: failed with invalid_parameter
08 rate limited: UPPU retries after 10, 60 and 300 seconds, then failed with provider_rate_limited
09 no answer: unknown, then delivered

Use numbers reserved for fiction in tests, for example +447700900100. Once ttl is switched on, a send to 08 with a short ttl (for example 30 seconds) ends sooner: UPPU stops retrying when the ttl runs out, and the message ends failed with provider_rate_limited, not expired, because the network answered with an error.

Template set (all in language tr): uppu_sim_order_update (UTILITY, two positional body parameters), uppu_sim_appointment (UTILITY, named customer_name and appointment_time, two quick-reply buttons), uppu_sim_payment (UTILITY, a text header with a variable), uppu_sim_campaign (MARKETING: needs a recorded consent), uppu_sim_login_code (AUTHENTICATION: always refused) and uppu_sim_pending (not approved).

Simulator endpoints (test key, messages:send):

Simulated traffic is not counted in the company's usage. UPPU can delete the data of simulated channels on request; real channels are never touched.

/v1/sandbox/action-target is not part of the API you call: on staging, UPPU Bot's actions call it in place of a company's own API. It needs no key and limits each client address; the reference describes it.

Production has no /v1/sandbox paths.

Your first message

The steps use staging and curl. Put the key into the environment first:

export UPPU_API_BASE=https://messaging-staging.uppu.net/v1
export UPPU_API_KEY=uppu_test_...

1. Find your channel. Note the id of the simulated channel.

curl -s "$UPPU_API_BASE/channels" -H "Authorization: Bearer $UPPU_API_KEY"

2. Let a person write to you. WhatsApp lets you send a free-form message only within 24 hours of the person's last message, so simulate one. The answer is a sandbox_event whose contact_id is the person.

curl -s -X POST "$UPPU_API_BASE/sandbox/inbound" \
  -H "Authorization: Bearer $UPPU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"channel_id": "<channel id>", "from": {"phone": "+447700900100", "name": "Deniz"}, "content": {"type": "text", "text": {"body": "Hello"}}}'

3. Reply. Every send carries an Idempotency-Key: the same key is reused for every retry of this send, so a retry never sends twice.

curl -s -X POST "$UPPU_API_BASE/messages" \
  -H "Authorization: Bearer $UPPU_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ticket-42-reply-1" \
  -d '{"channel_id": "<channel id>", "to": {"contact_id": "<contact id>"}, "content": {"type": "text", "text": {"body": "Thanks, we are on it."}}, "client_reference": "ticket-42"}'

The answer is 202 with the message, status queued.

4. Follow the status. GET /v1/messages/{id} shows it move to accepted, sent, delivered and read; with a webhook endpoint the same steps arrive as message.status events.

curl -s "$UPPU_API_BASE/messages/<message id>" -H "Authorization: Bearer $UPPU_API_KEY"

5. Send a template. An approved template also goes outside the 24-hour window:

{
  "channel_id": "<channel id>",
  "to": {"contact_id": "<contact id>"},
  "content": {
    "type": "template",
    "template": {"name": "uppu_sim_order_update", "language": "tr", "body": {"parameters": ["Deniz", "A-1001"]}}
  },
  "client_reference": "order-A-1001"
}

Sending messages

POST /v1/messages sends every kind of message; GET /v1/channels tells you what a channel accepts (capabilities.content_types, capabilities.limits).

The reply window and templates

Idempotency

Rate limits

LimitValue
Per key, every endpoint100 requests in any second and 3,000 per minute
Per key, sends and read receipts (POST /v1/messages, POST /v1/messages/{id}/read)20 requests per second
Per key, file uploads (POST /v1/media)2 requests per second and 1 GB per day
Per key, file downloads (GET /v1/media/{id}/content)10 requests per second and 2 GB per hour
Per key, missed events (GET /v1/events)2 requests per second
Typing indicators (POST /v1/conversations/{id}/typing)20 requests per second per key and one every 3 seconds per conversation
Per company, webhook endpoint test events and probes (POST /v1/webhook-endpoints/{id}/test and /probe)10 requests per minute, one at a time
Signed media links (POST /v1/media/{id}/link)A link works for 300 seconds and 30 requests; each download counts against the hourly media bytes of the key that issued it
Per key, reports (GET /v1/reports/summary and /daily)2 requests per second
Per company, template creates and deletes (POST /v1/templates, DELETE /v1/templates/{id})10 requests per minute, one at a time; Meta allows 100 new templates per hour for a WhatsApp account
Invalid keys from one network30 per minute, then every request gets 429 for 10 minutes
Waiting sends per channelFrom 10,000 waiting sends, or when the oldest has waited 15 minutes, a new send gets 429 channel_backlog

Errors

Every error has the same envelope:

{
  "error": {
    "type": "policy",
    "code": "window_closed",
    "status": 422,
    "retryable": false,
    "message": "The contact has not written in the last 24 hours: only an approved template can be sent.",
    "request_id": "01jace0e1v2m4n6p8q0r2s4t6a",
    "doc_url": "https://messaging.uppu.net/docs/errors#window_closed",
    "details": {"window_closed_at": "2026-10-19T08:58:00.000Z", "hint": "send_template"},
    "provider_error": null
  }
}

Webhooks

UPPU sends every event (a received message, a status, a changed identity) to your HTTPS endpoint as a signed POST. Verify the signature over the raw body, answer 2xx within 10 seconds, drop repeats by webhook-id and process the event afterwards. Events you missed stay available for 7 days at GET /v1/events. The webhooks page explains the set-up (in the panel or through the API), the signature, retries and every event with an example. To hand a file over without your key, POST /v1/media/{id}/link returns a signed download link that works for a few minutes; its downloads count against your key's hourly media bytes.

Handover to your team

On a channel your own system answers (handling api) or UPPU Bot answers (bot), the automation holds each conversation (held_by is api or bot) until it is handed over to your team (held_by team). Both calls need conversations:write (the Bot preset has it).

Templates through the API

With the templates:write scope, which no preset has (choose it under "custom"), your system creates and deletes the WhatsApp templates of a channel's WhatsApp Business account, with the rules of the panel's template page.

{
  "channel_id": "01j9z0ch5k2m4n6p8q0r2s4t6v",
  "name": "order_update",
  "language": "tr",
  "category": "UTILITY",
  "header": {"type": "text", "text": "Sipariş {{1}}", "example": "A-1001"},
  "body": {"text": "Merhaba {{1}}, siparişiniz yola çıktı.", "examples": ["Ayşe"]},
  "footer": {"text": "Bilgilendirme"},
  "buttons": [{"type": "quick_reply", "text": "Teşekkürler"}]
}

Reports

With the reports:read scope, also in no preset, GET /v1/reports/summary returns the figures of the panel's Reports page and GET /v1/reports/daily the same figures day by day. They are counted every night: counts only, no contact, conversation or message.

Compatibility

Within v1 UPPU adds, and never removes or renames:

Personal data

Going live