Platform
Chatbot Builder Bulk Messaging Team Inbox Mini CRM API & Webhooks AI Integration WhatsApp Flows
Industries
E-commerce & D2C Real Estate Education Healthcare Finance & BFSI Logistics Hospitality Retail
Integrations
Learn
Learning Hub Help & Docs Connect Guides Automation Codex Blog Message Templates
Pricing Start Free Trial →
HomeConnect › Connect Xero to WhatsApp
Xero Integration Guide · Accounting / Finance

Connect Xero to WhatsApp

Send WhatsApp payment reminders and invoice alerts from Xero. OAuth 2.0 with a 30-minute access token, the Xero-tenant-Id header on every call, signed webhooks that deliver only metadata (then you fetch the full record), and overdue invoice queries covered in full.

Published 23 June 2026  ·  8 min read  ·  Accounting / Finance
Xero access tokens expire after 30 minutes: refresh frequently

Xero access tokens have a 30-minute lifetime, the shortest of any accounting API in this series. You must refresh before each batch of calls or implement automatic refresh on 401 responses. Request the offline_access scope during authorisation to receive a refresh token (valid 60 days).

Xero webhooks send metadata only: always do a follow-up GET

When Xero fires a webhook for an invoice event, the payload contains only the InvoiceID, event type, and tenant ID. The invoice details, customer name, amount, and contact phone are not included. Your automation must call GET /api.xro/2.0/Invoices/{InvoiceID} to fetch the full record before sending the WhatsApp.

Step 1: Register a Xero app and authorise

1
Go to developer.xero.com, sign in, and click New app.
2
Choose Web app. Enter a name, company URL, and redirect URI (use https://localhost for self-service setup).
3
Note your Client ID and Client Secret from the app configuration page.
4
Direct the browser to the authorisation URL below to approve the connection and receive an authorisation code.
5
Exchange the code for access token and refresh token at the token endpoint.
OAuth 2.0 authorisation URL
https://login.xero.com/identity/connect/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://localhost
  &scope=openid profile email accounting.transactions accounting.contacts offline_access
  &state=random123

After approval, Xero redirects to:
  https://localhost?code=AUTHORISATION_CODE&state=random123

Exchange the code for tokens:
Exchange authorisation code for access and refresh tokens
POST https://identity.xero.com/connect/token
Authorization: Basic base64(CLIENT_ID:CLIENT_SECRET)
Content-Type: application/x-www-form-urlencoded

Body:
  grant_type=authorization_code
  &code=AUTHORISATION_CODE
  &redirect_uri=https://localhost

Response:
{
  "access_token":  "eyJhbGciOiJSUzI1...",
  "refresh_token": "c4ece16d-d...",
  "expires_in":    1800,
  "token_type":    "Bearer"
}

expires_in is 1800 seconds (30 minutes). Refresh before it expires.
Official docs

Xero developer documentation: developer.xero.com

Step 2: Get the tenant ID

The Xero-tenant-Id header is required on every API call. Retrieve it from the connections endpoint immediately after authorisation.

GET /connections — list authorised tenants
GET https://api.xero.com/connections
Authorization: Bearer YOUR_ACCESS_TOKEN

Response:
[
  {
    "id":          "abc123-def456",
    "tenantId":    "45e4708e-d862-4111-ab3a-dd8cd03913e1",
    "tenantName":  "ABC Traders",
    "tenantType":  "ORGANISATION"
  }
]

Store tenantId. Add to every subsequent call:
  Xero-tenant-Id: 45e4708e-d862-4111-ab3a-dd8cd03913e1

Step 3: Refresh the access token

Refresh access token (run before every batch of calls, or on 401)
POST https://identity.xero.com/connect/token
Authorization: Basic base64(CLIENT_ID:CLIENT_SECRET)
Content-Type: application/x-www-form-urlencoded

Body:
  grant_type=refresh_token
  &refresh_token=YOUR_REFRESH_TOKEN

Response: new access_token (30-minute lifetime).
Refresh token is rotated: store the new refresh token from the response.
Refresh tokens rotate on each use

Every time you refresh the access token, Xero returns a new refresh token alongside the new access token. Store the new refresh token each time. The previous one is invalidated. If you use an old refresh token, the connection is revoked and you must re-authorise.

Direction A: Xero webhook triggers a WhatsApp

Set up the webhook subscription

1
In the Xero Developer portal, open your app and click Webhooks.
2
Click Add subscription. Select Invoice events and enter your WA.Expert automation webhook URL.
3
Copy the Webhooks Key (signing key) shown on the page. You need this to verify the x-xero-signature header.
4
Xero sends an Intent to Receive ping to your URL immediately. Your endpoint must respond 200 within 5 seconds for the subscription to activate.
Xero webhook payload — invoice event (metadata only)
POST to your WA.Expert webhook URL:

Headers:
  x-xero-signature: HMAC-SHA256 of raw body using your Webhooks Key

Body:
{
  "events": [
    {
      "resourceUrl":  "https://api.xero.com/api.xro/2.0/Invoices/a1b2c3d4",
      "resourceId":   "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "eventDateUtc": "2026-06-23T21:30:00.000Z",
      "eventType":    "CREATE",
      "eventCategory":"INVOICE",
      "tenantId":     "45e4708e-d862-4111-ab3a-dd8cd03913e1"
    }
  ]
}

This is METADATA ONLY. No invoice amount, no customer name, no phone.
Follow up with GET /Invoices/{resourceId} to get full details.
Follow-up GET to fetch full invoice after webhook
GET https://api.xero.com/api.xro/2.0/Invoices/a1b2c3d4-e5f6-7890-abcd-ef1234567890
Authorization: Bearer YOUR_ACCESS_TOKEN
Xero-tenant-Id: 45e4708e-d862-4111-ab3a-dd8cd03913e1
Accept: application/json

Response (simplified):
{
  "Invoices": [{
    "InvoiceID":     "a1b2c3d4...",
    "InvoiceNumber": "INV-0142",
    "Status":        "AUTHORISED",
    "AmountDue":     48500.00,
    "DueDateString": "2026-07-07",
    "Contact": {
      "Name": "Priya Textiles",
      "Phones": [
        {"PhoneType": "MOBILE", "PhoneNumber": "9820012345"}
      ]
    }
  }]
}

Map:
  Contact.Name               -> party_name
  "+91" + Phones[MOBILE]     -> wa_phone  <- PREPEND +91
  InvoiceNumber              -> invoice_number
  AmountDue                  -> amount_due
  DueDateString              -> due_date

Direction B: Query Xero for overdue invoices

GET overdue invoices — payment reminder automation
GET https://api.xero.com/api.xro/2.0/Invoices
    ?Statuses=AUTHORISED
    &Type=ACCREC
    &where=AmountDue%3E0
Authorization: Bearer YOUR_ACCESS_TOKEN
Xero-tenant-Id: 45e4708e-d862-4111-ab3a-dd8cd03913e1
Accept: application/json

Alternatively filter by date:
    &DueDateFrom=2026-01-01&DueDateTo=2026-06-23

Response: { Invoices: [{ InvoiceNumber, AmountDue, DueDateString,
                          Contact: { Name, Phones } }] }

For each invoice where AmountDue > 0 and DueDateString < today:
  Send WhatsApp payment reminder to Contact mobile.

Troubleshooting

SymptomLikely causeFix
401 on API callsAccess token expired (30-minute lifetime)Refresh the access token using the refresh token. Implement auto-refresh on 401.
Webhook subscription not activatingIntent to Receive ping failedYour endpoint must respond 200 within 5 seconds to the initial Intent to Receive ping. Ensure the WA.Expert automation is published and responding.
x-xero-signature mismatchSignature verification failingCompute HMAC-SHA256 of the raw (unparsed) request body using your webhook signing key. Compare to the x-xero-signature header value. Return 401 on mismatch.
No invoice data in webhook payloadXero webhooks are metadata-onlyAfter receiving the webhook, call GET /Invoices/{resourceId} to fetch the full record.
Refresh token invalidOld refresh token used after rotationXero rotates refresh tokens on each use. Always store and use the new refresh token returned in the refresh response.
Xero-tenant-Id missingHeader omittedEvery Xero API call requires the Xero-tenant-Id header. Retrieve it once from GET /connections and include it on all subsequent calls.

Common questions

What is the Xero-tenant-Id header?
+
It identifies which Xero organisation to access. A single OAuth connection can cover multiple organisations. Get it from GET https://api.xero.com/connections.
How long do Xero access tokens last?
+
30 minutes, the shortest of any accounting API in this series. Refresh before each batch of calls or on any 401 response. Refresh token lasts 60 days and rotates on each use.
Why does the webhook payload have no invoice data?
+
Xero webhooks are metadata-only. They contain the InvoiceID and event type, not the invoice amounts or contact details. Follow up with GET /Invoices/{id}.
Do I need to verify the webhook signature?
+
Yes. Compute HMAC-SHA256 of the raw body using your Webhooks Key. Compare to x-xero-signature. Return 200 on match, 401 on mismatch.
How do I get a contact's phone number?
+
From the invoice Contact.Phones array, find the entry with PhoneType MOBILE. The PhoneNumber field has the number. Prepend +91 for bare Indian 10-digit numbers.
Does this incur extra WA.Expert charges?
+
Each WhatsApp uses a message credit: Rs. 0.14 on Starter (utility). Xero rate limit: 60 requests per minute per tenant.

Connect Zoho Books to WhatsApp

Zoho Books: OAuth 2.0, India domain (.in), workflow webhooks for invoices.

Read guide

Connect ERPNext to WhatsApp

ERPNext: token auth, DocType API, built-in webhook on invoice submit.

Read guide

External API Request Step

Master every field in WA.Expert's HTTP action step.

Read guide

Connect Xero to WhatsApp today

Free trial, no credit card. If you get stuck, we answer live on WhatsApp.

Start Free Trial → Book a Demo
1