From your phone
to your project.
Connect your own WhatsApp to your application. Each account has its own QR code, API key, connection and message activity. Public accounts do not have SMS access.
simulated result means no message reached WhatsApp. QR linking is independent from sending. Automatic payments are not configured.1. Create your account and save your key
Enter a project name, email address and password with at least 12 characters on My account. Accept the pilot terms. When enabled, you can also sign up or sign in with Google. If you already use a password, log in first and link Google from your dashboard using the same email address to keep your project, trial and number.
Your 7-day trial starts at registration. Save your API key immediately: it is shown only when generated. “Generate new key” invalidates the previous key without changing your WhatsApp session. Keep it in private backend environment variables; never include it in a browser, APK, URL, screenshot or public repository. Mobile apps call your backend, which calls OswiLink.
2. Link your phone
- Enter your number in international format, such as the fictional example
+13015550123, and select “Show my QR code”. - On your phone, open WhatsApp → Linked devices → Link a device.
- Scan the QR code from another screen and wait for “Connected”. Expired QR codes refresh in the dashboard.
You need WhatsApp already registered on your phone. The QR code does not register a new account or replace that phone. Only the number you entered is accepted, and it cannot belong to another OswiLink account. Your private QR code requires your account session and is not logged.
Each account supports one active line. “Unlink or change number” stops the connection, cancels pending messages and removes local WhatsApp credentials. Your account, API key and plan remain. If remote logout is not confirmed, also remove the linked device in WhatsApp. If you unlink from your phone, the dashboard detects it and prepares a new QR code when you return. To use a different number, unlink first and update the number.
3. Configure your backend
The base URL is https://oswilink.com, without /docs. Local server ports and the operator’s private SMS gateway are not for public accounts. Always use HTTPS.
OSWI_API_URL=https://oswilink.com
OSWI_API_KEY=YOUR_PRIVATE_KEY
Authorization: Bearer YOUR_PRIVATE_KEY
Content-Type: application/json4. Your application controls sending
OswiLink provides the technical messaging channel. Your application chooses the recipients, content and sending time. No separate recipient authorization endpoint is required to send WhatsApp messages through this API.
Incoming replies, including STOP and START, are delivered through events or webhooks. Your integration manages permissions, recipient lists and opt-out requests. These words do not automatically block a recipient in OswiLink. Pilot limits and service security controls still apply.
5. Send a message
curl "$OSWI_API_URL/v1/messages" \
-H "Authorization: Bearer $OSWI_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: visit-123-confirmation-v1' \
--data '{"channel":"whatsapp","to":"+13015550123","text":"Your visit is confirmed for tomorrow.","ttl_seconds":3600}'The initial response is HTTP 202 with message.id and status queued. Store the message ID. Text supports up to 4096 UTF-16 units. Groups, campaigns and broadcast lists are not included.
Store an idempotency key per business operation. Retrying the same body with the same key returns the same message. Changing the body with the same key returns HTTP 409. Deduplication lasts 30 days; do not resend old operations after that window. Keys must contain 8–128 letters, numbers, periods, hyphens, underscores or colons. After an HTTP timeout, retry the same body and key.
6. Track delivery
curl "$OSWI_API_URL/v1/messages/MESSAGE_ID" \
-H "Authorization: Bearer $OSWI_API_KEY"| Status | Meaning |
|---|---|
| queued / sending | Waiting / send attempt in progress. |
| simulated | Internal test; no real message was sent. |
| submitted | Accepted by the provider; delivery is not yet confirmed. |
| delivered / read / played | Delivery, reading or playback receipt, when reported by WhatsApp. Read receipts are not guaranteed. |
| unknown | Outcome could not be confirmed. Do not resend with a new key; that could create a duplicate. |
| blocked / failed / expired / cancelled | Blocked, confirmed failure, expired or cancelled. |
| received | Incoming message for your WhatsApp account. |
Cancel a queued message with POST /v1/messages/ID/cancel. Messages already in transit cannot be recalled through this endpoint.
7. Receive replies and events
curl "$OSWI_API_URL/v1/events?after=0" \
-H "Authorization: Bearer $OSWI_API_KEY"Responses contain data and next_cursor. Process and persist each event before advancing your stored cursor. Deduplicate by event id. Each page contains up to 100 events; poll every 5–10 seconds when no further pages are available. Use GET /v1/messages for a quick view and events for synchronization.
Accounts see only their own messages. Historical chats and group messages are not imported. Events last about 24 hours; longer outages may lose content. A webhook delivers the same events automatically but does not extend retention.
Images, documents, audio and video
Use POST /v1/messages with content_type and media. Text defaults to text. The pilot limit is 8 MiB per file. Sources must be public HTTPS URLs with public IPv4 resolution, no redirects, URL credentials, alternate ports or private network destinations.
{
"channel": "whatsapp",
"to": "+13015550123",
"content_type": "document",
"text": "Your invoice is attached.",
"media": {
"url": "https://files.YOUR-DOMAIN/invoice-123.pdf",
"mimetype": "application/pdf",
"filename": "invoice-123.pdf"
},
"ttl_seconds": 3600
}
| content_type | Formats | Optional text |
|---|---|---|
| image | image/jpeg, image/png, image/webp | Caption, up to 1024 characters. |
| video | video/mp4, compatible with WhatsApp. | Caption, up to 1024 characters. |
| audio | audio/mpeg, audio/ogg, audio/mp4, audio/aac; compatible codecs required. | None; omit text. |
| document | File MIME type, such as application/pdf. | Caption, up to 1024 characters. |
Previous pilot tests received read receipts for PNG, JPG, MP4, MP3, M4A, AAC, PDF, TXT and ZIP. WebP and OGG were accepted by the provider without confirmed delivery. Format acceptance does not guarantee delivery or playback on every phone.
Filenames allow up to 120 ASCII letters, numbers, spaces, periods, hyphens and underscores, starting with a letter or number. Host files on your infrastructure. Signed URLs must remain valid until sending, which may happen after HTTP 202. Inaccessible, oversized or blocked files produce failed with media_rejected_before_send. Simulation does not download files or verify their existence or compatibility.
Incoming attachments appear in message.received with data.media.filename, mimetype, size and download_path. Download from your backend:
curl "$OSWI_API_URL/v1/messages/INBOUND_ID/media" \
-H "Authorization: Bearer $OSWI_API_KEY" \
--output received-fileA working WhatsApp session is required. Only your account’s attachments can be downloaded, for about 24 hours. HTTP 410 means unavailable or expired; HTTP 413 means too large. Provider URLs and encryption keys are not exposed. View-once content is not extracted. Your application must validate attachments; antivirus scanning is not guaranteed.
Webhooks for replies and status changes
Set your backend HTTPS endpoint in the dashboard or API:
curl -X PUT "$OSWI_API_URL/v1/webhook" \
-H "Authorization: Bearer $OSWI_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"url":"https://YOUR-APP/webhooks/oswi"}'Save webhook_secret in your backend. It is separate from your API key. GET /v1/webhook returns the URL without the secret. POST /v1/webhook/rotate-secret with {} rotates it. Set {"url":null} to disable. The same public HTTPS restrictions apply as for media URLs.
{
"id": "EVENT_UUID",
"type": "message.received",
"created_at": 1789850000000,
"data": {
"id": "UUMESSAGE_ID",
"direction": "inbound",
"phone": "+13015550123",
"body": "Yes, I confirm the visit",
"content_type": "text",
"status": "received"
}
}
message.queued reports acceptance, message.updated reports status changes and message.received carries incoming replies. Attachments add data.media. Message status means delivery status, not WhatsApp Status/Stories.
Headers are X-Oswi-Event-Id, X-Oswi-Timestamp in Unix seconds and X-Oswi-Signature: sha256=.... Verify HMAC-SHA256 over timestamp + "." + original HTTP body with a constant-time comparison and reject timestamps more than five minutes away. Do not reserialize JSON before verification. The source package includes src/webhook-signature.mjs and a Node 24 receiver in examples/webhook-receiver.mjs. These header names remain stable after the OswiLink rename.
Persist and deduplicate each event before returning HTTP 2xx. Delivery retries up to eight times with progressive backoff, a five-second timeout and no redirects. Events may arrive twice or out of order; do not regress a status based on an older event. Use /v1/events for recovery within its 24-hour window.
8. JavaScript / Node.js
// Backend only. Never in browser JavaScript.
const response = await fetch(process.env.OSWI_API_URL + '/v1/messages', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.OSWI_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-456-confirmation-v1'
},
body: JSON.stringify({
channel: 'whatsapp', to: '+13015550123', text: 'Order confirmed.'
}),
signal: AbortSignal.timeout(15000)
});
const result = await response.json();
if (!response.ok) throw new Error(result.error);
// Store result.message.id in your application database.9. PHP / Laravel
Configure services.oswi.url and services.oswi.key using private environment variables in config/services.php.
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.oswi.key'))
->timeout(15)
->withHeaders(['Idempotency-Key' => 'visita-'.$visit->id.'-v1'])
->post(config('services.oswi.url').'/v1/messages', [
'channel' => 'whatsapp',
'to' => $customer->phone_e164,
'text' => 'Your visit is confirmed.',
]);
if (!$response->successful()) {
throw new RuntimeException($response->json('error', 'request_failed'));
}
$messageId = $response->json('message.id');Run sends in a backend queue. Retry with the same body and idempotency key after timeouts. Do not log Authorization headers.
10. Trial, subscriptions and errors
The pilot includes seven days, 20 messages per day and one connection per account. Current capacity is 25 accounts and five simultaneous connections. Manually activated paid accounts have a 100-message daily limit; this is not yet a published commercial offer or price.
When entitlement expires, new sends return HTTP 402, pending sends are blocked and WhatsApp disconnects. You can still log in and view retained data. Cards, automatic billing and subscription checkout are not active.
| HTTP | Action |
|---|---|
| 400 / 415 | Check JSON, international phone format and Content-Type. |
| 401 | Invalid/revoked API key, or log in again in the portal. |
| 402 | Trial/subscription expired; activate before retrying. |
| 403 | Suspended account, non-WhatsApp channel or recipient outside the pilot allowlist. |
| 404 | Message absent or owned by another account. |
| 409 | Idempotency conflict, live sending disabled or linking unavailable. |
| 429 | Daily or request limit. Wait; do not change keys to bypass limits. |
| 503 | Temporary connection or capacity issue. Retry with backoff. |
11. Before a commercial launch
Baileys is an unofficial library and is not affiliated with or authorized by WhatsApp. Provider changes, disconnections and account restrictions may occur. See the Baileys project disclaimer.
The portal has HTTPS and targeted live messaging tests. Email verification/recovery, complete terms, account deletion procedures, backup policy and restore tests, load tests, external webhook validation, abuse controls and payment validation remain launch requirements. Do not present the pilot as an official WhatsApp API or a guaranteed-uptime service.
12. Privacy and retention
We are not a permanent conversation archive. Content, source URLs and events are retained for about 24 hours; technical message metadata for up to 30 days. Account and connection credentials have separate retention. Attachment bytes are processed on demand. Read the privacy notice and pilot terms. Your application determines its own storage.