INVOICE DELIVERY API
Your invoice link, delivered on WhatsApp
Your POS, website or billing platform creates and hosts the invoice. Its backend calls WhatsOS with the recipient’s phone number and invoice URL. WhatsOS sends an approved message containing the link to view or download that invoice, using your connected business number.
WhatsOS does not need access to your POS or platform. Your developer implements the API call on your side. PDF attachments are optional.
1. Configure once
- Create a sandbox project in Bills & Receipts.
- Open Templates and sync. Select
demo_invoice_linkand save it as the invoice-link template. For live delivery, select an approved utility text template with exactly one body variable for the URL, such as Your invoice is ready. View or download it here: {{1}}. - Create an API key with
messages:sendandmessages:read. Keep it on your backend. - Enable project sending in Settings. Live delivery also requires an active service subscription and sender/template readiness. Existing valid workspace connections are reused.
No POS credentials, field mapping, documentation upload or vendor-specific connection is needed for this endpoint. Sandbox requests simulate delivery and never send a real WhatsApp message.
2. Call WhatsOS after the invoice is ready
POST https://whatsos.com/api/v1/messaging/invoices
{
"to": "+919876543210",
"invoice_url": "https://billing.example.com/invoices/1042",
"reference": "INV-1042",
"consent": {
"obtained": true,
"source": "Customer requested WhatsApp invoice at checkout"
}
}to is the recipient’s international phone number. invoice_url is your hosted HTTPS view/download URL, up to 1,024 characters. The invoice can be a web page or a PDF; a PDF file is not required. Use a customer-accessible link without browser login, cookies or authorization headers. Keep it valid long enough for the customer to use it.
reference is optional but recommended for invoice tracking. If omitted, WhatsOS derives it from the idempotency key. Record the customer’s permission in your own system and supply the consent fields.
Save the request as invoice.json:
curl https://whatsos.com/api/v1/messaging/invoices \
-H "Authorization: Bearer $WHATSOS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-1042-v1" \
--data-binary @invoice.jsonThe API key determines the client project, connected WhatsApp number and saved template. You do not send Meta credentials or the template name with each invoice request.
Node.js example
import { WhatsosMessages } from './whatsos-messages.mjs';
const client = new WhatsosMessages({ apiKey: process.env.WHATSOS_API_KEY });
const result = await client.sendInvoice({
to: invoice.customerPhone,
invoice_url: invoice.viewUrl,
reference: invoice.number,
consent: { obtained: true, source: 'Checkout WhatsApp invoice consent' }
}, { idempotencyKey: invoice.whatsosOperationKey });
await saveMessageId(invoice.id, result.message_id);
const delivery = await client.get(result.message_id);Persist whatsosOperationKey with the invoice before sending. Reuse the same key and unchanged request when retrying.
PHP example
$request = [
'to' => $invoice['customer_phone'],
'invoice_url' => $invoice['view_url'],
'reference' => $invoice['number'],
'consent' => ['obtained' => true, 'source' => 'Checkout WhatsApp invoice consent']
];
$ch = curl_init('https://whatsos.com/api/v1/messaging/invoices');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('WHATSOS_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . $invoice['whatsos_operation_key']
],
CURLOPT_POSTFIELDS => json_encode($request)
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
// Store the message_id on HTTP 202. Retry timeouts with the same key.3. Track delivery and handle retries
A successful request returns 202 Accepted with a message_id. This confirms queue acceptance, not customer delivery.
GET /api/v1/messaging/messages/{message_id}
Authorization: Bearer YOUR_API_KEYFollow queued, submitting, accepted, sent, delivered and read statuses in the API or Delivery history. Signed callbacks can notify your backend of transaction events. Verify callback signatures using your project’s callback secret.
- Use the same Idempotency-Key for retries. Changed content returns a conflict instead of sending a duplicate.
- The invoice reference and recipient also protect against duplicate sends. Keep references unique per invoice.
- Unknown provider outcomes are held for review; do not create a fresh key to resend them.
- Pending messages expire after 24 hours. Keep the invoice URL valid beyond this window.
- Customer opt-outs remain enforced; the consent flag cannot override an opt-out.
- Existing subscription, utility-message pricing and spending limits apply.
Optional PDF attachments and advanced messages
To send an actual PDF attachment, use POST /api/v1/messaging/messages with a PDF-header utility template and the document field. You can provide a hosted PDF URL or use the existing PDF upload endpoint. These are optional; the simple invoice-link endpoint does not require an upload.
{
"to": "+919876543210",
"kind": "invoice",
"reference": "INV-1042-PDF",
"template": {
"name": "demo_invoice_pdf", "language": "en_US",
"parameters": ["Alex", "INV-1042", "AED 250.00"]
},
"document": {"url": "https://billing.example.com/invoices/1042.pdf", "filename": "invoice.pdf"},
"consent": {"obtained": true, "source": "Customer requested WhatsApp invoice"}
}The advanced endpoint also supports text-only utility templates with the invoice HTTPS link in a body variable. Existing configured connectors continue to work, but new API callers do not need them.
Common responses
| Code | Next step |
|---|---|
| 401 INVALID_KEY | Check API key expiry or revocation. |
| 403 MISSING_SCOPE | Use a key with messages:send. |
| 409 INVOICE_TEMPLATE_REQUIRED | Sync and save a one-variable utility text template in Templates. |
| 403 MESSAGING_NOT_READY | Check project sending, template sync and service activation. |
| 403 RECIPIENT_OPTED_OUT | The recipient must provide renewed permission. |
| 409 IDEMPOTENCY_CONFLICT / REFERENCE_CONFLICT | The existing operation has different content. Do not blindly resend. |
| 400 validation error | Check the phone number, HTTPS URL, consent and idempotency header. |
| 429 RATE_LIMITED | Retry later using the same operation key. |
