WebSMS Connexus API

Simple, powerful SMS API for sending messages, receiving replies, and managing delivery reports

Migrating from Bulletin Connect? See our simple 3-step migration guide

Function Endpoint
Get Access Token https://api.websms.co.nz/api/connexus/auth/token
Send SMS https://api.websms.co.nz/api/connexus/sms/out
Webhooks (Incoming SMS & Delivery Reports) Configure in Members Area
Query Incoming Messages https://api.websms.co.nz/api/connexus/mo/query
Query Delivery Status https://api.websms.co.nz/api/connexus/sms/status/query
Billing Report (by rateCode) https://api.websms.co.nz/api/connexus/billing/query
Query Blocked Numbers https://api.websms.co.nz/api/connexus/unsubscribes/query
Check Balance https://api.websms.co.nz/api/connexus/sms/balance
Send OTP/2FA Code https://api.websms.co.nz/api/connexus/sms/otp
Appointment Reminder https://api.websms.co.nz/api/connexus/sms/appointment
Number Validation (IPMS) https://api.websms.co.nz/api/connexus/number/lookup
Message Class (Transactional / Marketing) Add messageClass=transactional or messageClass=marketing to every send
Sandbox / Testing Mode Add sandbox=true to any send request

The Connexus API supports two authentication methods:

Username/Password
Basic Auth

Simple authentication using your WebSMS email and password. Best for quick testing and simple integrations.

curl -X POST https://api.websms.co.nz/api/connexus/sms/out \
  -d "userId=user@domain.co.nz" \
  -d "password=yourpassword" \
  -d "to=6421234567" \
  -d "body=Hello" \
  -d "messageClass=transactional"
API Keys (OAuth2-style)
Recommended

More secure authentication using client credentials. Best for production applications.

  1. Create an API key in your dashboard
  2. Exchange credentials for an access token
  3. Use the token in your API calls
Learn more

Recommended for production: API keys provide better security than username/password authentication. Your main password stays protected, and you can manage multiple keys with different expiry dates.

Step 1: Create an API Key

Go to API Keys in your dashboard to create a new key. You'll receive:

  • Client ID (cid_xxx...) - Public identifier
  • Client Secret (csk_xxx...) - Keep this secret!
The client secret is only shown once when you create or roll the key. Store it securely!

Step 2: Get an Access Token

Exchange your client credentials for an access token:

curl -X POST https://api.websms.co.nz/api/connexus/auth/token \
  -d "client_id=cid_abc123..." \
  -d "client_secret=csk_xyz789..."
Response:
{
  "access_token": "wst_abc123...",
  "token_type": "Bearer",
  "expires_in": 86400
}

Step 3: Use the Token

Include the access token in the Authorization header:

curl -X POST https://api.websms.co.nz/api/connexus/sms/out \
  -H "Authorization: Bearer wst_abc123..." \
  -d "to=6421234567" \
  -d "body=Hello World" \
  -d "messageClass=transactional"
Token Expiry: Access tokens are valid for 24 hours. When expired, request a new token using your client credentials. Your client_id and client_secret don't expire (unless you set an expiry date on the key).

Token Endpoint Details

URL https://api.websms.co.nz/api/connexus/auth/token
Method POST
Content-Type application/x-www-form-urlencoded or application/json
Error Responses:
HTTP Code Error Description
400 invalid_request Missing client_id or client_secret
401 invalid_client Invalid credentials, key disabled, or key expired
405 method_not_allowed Only POST method is accepted

Key Management Features

  • Multiple keys: Create up to 10 keys per account
  • Key expiry: Set optional expiry dates on keys (we'll remind you 60 and 30 days before)
  • Roll secrets: Regenerate a key's secret without changing the client_id
  • Enable/Disable: Temporarily disable a key without deleting it
  • Usage tracking: See when each key was last used

URLs require approval. Any domain or website you include as a link in a message must be whitelisted before your messages will be sent.

To help protect recipients from phishing and spam, all links in outbound messages are checked against your account's list of approved domains. Messages containing links to a domain that hasn't been whitelisted will be held until the domain is approved.

Whitelisting is done within our portal — open the Request whitelist form, enter your domain, and submit. Once approved, messages containing links to that domain are sent immediately.

  • Whitelisting applies to the domain (e.g. example.com), so you only need to request each domain once — not every individual URL.
  • Request whitelisting before you start sending, so your first campaign isn't delayed waiting for approval.

Overnight messages are queued, not sent. By default, messages to New Zealand numbers submitted between 11:00pm and 7:00am NZ time are held in a queue and released automatically at 7:00am NZ time.

This is a quiet-hours restriction applied for you so that sending stays within the Appropriate Timing requirement of our Terms of Service — messages should reach recipients at reasonable hours. Nothing is lost or rejected: the API still accepts the request and returns a message ID you can track as normal.

Response for a queued message
{
  "success": true,
  "status": "queued",
  "message_id": "1158493",
  "to": "6421234567",
  "from": "552",
  "parts": 1,
  "queue_reason": "nighttime",
  "queue_message": "Message queued for delivery at 7am NZ time"
}

Check for status: "queued" rather than status: "accepted" if your integration needs to distinguish the two. Delivery reports for a queued message arrive after it is released, not at submission time.

What is and isn't affected
  • New Zealand numbers only. The restriction is applied to destinations starting 64. International destinations are never night-queued, so send those according to the recipient's own local time.
  • OTP / 2FA codes are exempt. Sends to /api/connexus/sms/otp bypass the queue entirely and go out immediately, day or night — a login code is useless the next morning.
  • Everything else is queued, including appointment reminders and transactional messages. messageClass does not change this.
Need 24×7 sending? Accounts that legitimately need to send overnight — emergency and after-hours notifications, monitoring and outage alerts, on-call dispatch — can be exempted. Email support@websms.co.nz with your account and a brief description of the use case and we'll enable it. Once exempt, overnight messages are submitted immediately with status: "accepted" and no further changes are needed on your side.
Opt-outs on your own shortcode

On our shared shortcodes we handle opt-outs for you: an inbound STOP (or any of the accepted opt-out keywords) blocks that number on your account automatically, and you can read the list back via Query Blocked Numbers.

If you operate your own shortcode and have registered an MO webhook, opt-out handling is yours. Inbound messages on a dedicated shortcode are delivered straight to your webhook untouched — we do not scan them for opt-out keywords, do not add the number to a blocked list, and do not send a confirmation reply. That means we will not stop you sending to a number that has asked you to stop. You are responsible for detecting opt-outs in your webhook, suppressing further sends to that recipient, and honouring the request, as required by our Terms of Service. This suits resellers and platforms serving multiple end customers on one shortcode, where opt-out state belongs per end customer rather than across the whole shortcode.

Send your first SMS in seconds:

Using API Key Token (Recommended)
curl -X POST https://api.websms.co.nz/api/connexus/sms/out \
  -H "Authorization: Bearer wst_your_token..." \
  -d "to=6421234567" \
  -d "body=Hello World" \
  -d "messageClass=transactional"
Using Username/Password
curl -X POST https://api.websms.co.nz/api/connexus/sms/out \
  -d "userId=user@domain.co.nz" \
  -d "password=yourpassword" \
  -d "to=6421234567" \
  -d "body=Hello World" \
  -d "messageClass=transactional"
Response
{
  "success": true,
  "status": "accepted",
  "message_id": "1158493",
  "to": "6421234567",
  "from": "552",
  "parts": 1,
  "route": "smsc_2degrees",
  "porting": {
    "ported": false,
    "carrier": "2degrees",
    "source": "redis"
  }
}

The route and porting fields are included for NZ numbers when carrier information is available.

Send the same message to multiple recipients in a single API call using JSON body with to as an array:

Limit: Maximum 200 recipients per bulk request. Requests exceeding this limit will be rejected with HTTP 400. Split larger sends into multiple requests.
Request
curl -X POST https://api.websms.co.nz/api/connexus/sms/out \
  -H "Authorization: Bearer wst_your_token..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["6421111111", "6422222222", "6423333333"],
    "body": "Your appointment is tomorrow at 2pm",
    "messageClass": "transactional"
  }'
Response
{
  "success": true,
  "messages": [
    {
      "success": true,
      "status": "accepted",
      "message_id": "1188236",
      "to": "6421111111",
      "from": "552",
      "parts": 1,
      "route": "smsc_vodafone",
      "porting": {"ported": true, "carrier": "One NZ", "source": "redis"}
    },
    {
      "success": true,
      "status": "accepted",
      "message_id": "1188237",
      "to": "6422222222",
      "from": "552",
      "parts": 1,
      "route": "smsc_vodafone"
    },
    {
      "success": true,
      "status": "accepted",
      "message_id": "1188238",
      "to": "6423333333",
      "from": "552",
      "parts": 1,
      "route": "smsc_2degrees"
    }
  ],
  "summary": {
    "total": 3,
    "sent": 3,
    "failed": 0
  }
}

Each message in the messages array has the same format as a single SMS response. The summary provides totals for easy tracking.

Parameter Required Description Example
userId Required Your WebSMS account email user@domain.co.nz
password Required Your WebSMS account password mypassword
to Required Recipient phone number (international format). For bulk sends, use JSON body with to as an array. 6421234567 or ["6421111111","6422222222"]
body Required Message content. Sent as GSM-7 — see Message encoding, as characters outside that set are not delivered as typed. Hello World
messageClass Required Classifies the message so it can be routed over the correct shortcode. Must be transactional (one-to-one, user-initiated messages such as OTPs, alerts and notifications) or marketing (promotional or bulk campaign traffic). See Message Class for details. transactional
from Optional Sender ID 552
messageId Optional Unique message identifier (max 36 chars) MSG123456
rateCode Optional Message source identifier (max 95 chars) CAMPAIGN1
contentType Optional Message type text/plain
fragmentationLimit Optional Maximum SMS parts (1–11, or 0 for no limit of your own). 11 parts is 1683 characters. A longer message is trimmed to fit, and the response then carries truncated: true. 11
sandbox Optional If true, validates and returns a demo response without sending the SMS or billing your account. Useful for integration testing. true
Message encoding & length

Messages are sent as GSM-7. Before sending, we map the common typographic characters to their plain equivalents — curly quotes become ', en/em dashes become -, becomes ..., and accented and macronised vowels are folded to unaccented letters (Māori is sent as Maori).

Any remaining character outside the ASCII range — emoji, currency symbols such as , and non-Latin scripts — is replaced with ?. This endpoint does not send UCS-2/Unicode. Use sandbox=true and read sandbox_message back to see exactly what would go out.

A single SMS holds 160 characters. Longer messages are split into parts of 153 characters each, up to the fragmentationLimit11 parts, or 1683 characters. Each part is billed separately, so a 1683-character message costs 11× a single SMS. The parts field in the response tells you what you were charged for.

Response Codes

204 Success - Message sent
400 Bad Request - Missing parameters
401 Unauthorized - Invalid credentials
403 Forbidden - Insufficient funds
500 Server Error

Pricing

Standard SMS (160 chars) $0.10 +GST
Multi-part SMS (per part) $0.10 +GST
Unicode SMS (70 chars) $0.10 +GST
GST applies to NZ customers only

Every send must declare a messageClass so we can route it over the appropriate shortcode. Choose the value that matches the intent of the message:

Value Use for Examples
transactional One-to-one, user-initiated or service messages the recipient is expecting. OTP / 2FA codes, booking confirmations, delivery updates, account alerts, appointment reminders
marketing Promotional or bulk campaign traffic sent to a list. Sales and offers, newsletters, product announcements, event invites
Terms of Service. Sending messages using WebSMS means you have accepted our Terms of Service. Please refer to the Terms for examples of transactional and marketing message types, and for the rules that apply to each.

Classifying marketing traffic correctly improves deliverability and keeps your transactional messages on high-trust routes. Misclassifying marketing traffic as transactional may be treated as a compliance issue.

Add sandbox=true to any request to /sms/out, /sms/otp, or /sms/appointment to validate your integration without actually sending an SMS. In sandbox mode WebSMS will:

  • Authenticate your credentials and validate all parameters (you still get real 400/401/403 errors for bad inputs or unauthorised shortcodes)
  • Echo back the exact message body we received after smart-quote / GSM-7 conversion and any fragmentationLimit truncation, so you can confirm what would have been sent
  • Return the detected encoding, message parts, and what the send would have cost
  • Respond with a SANDBOX_ message ID and "sandbox": true

Nothing is queued, sent, logged to your message history, or billed to your account. Balance is not checked, so sandbox requests work even with a zero balance.

Field naming: Any field prefixed with sandbox_ is returned only in sandbox mode — don't expect it in a real send response. Use it as a signal that the call did not actually deliver anything.

Example Request:

curl -X POST https://api.websms.co.nz/api/connexus/sms/out \
  -d "userId=user@domain.co.nz" \
  -d "password=yourpassword" \
  -d "to=6421234567" \
  -d "body=Hello from sandbox" \
  -d "messageClass=transactional" \
  -d "sandbox=true"

Example Response:

{
  "success": true,
  "status": "sandbox",
  "message_id": "SANDBOX_671e4d8a9f3c2",
  "to": "6421234567",
  "from": "2190",
  "parts": 1,
  "sandbox": true,
  "sandbox_message": "Hello from sandbox",
  "sandbox_encoding": "GSM-7",
  "sandbox_would_charge": 0.092
}
Tip: sandbox_would_charge is GST-inclusive and reflects your account's rate (or the $0.08 template rate for OTP/appointment, whichever is lower). sandbox_message is useful for spotting unintended character conversions (e.g. a smart apostrophe that becomes ?, or a long message clipped by fragmentationLimit).
No delivery reports: Because nothing is actually sent, no DLR webhook will fire for sandbox messages.

Configure a single webhook URL to receive both incoming SMS messages and delivery status updates

Simplified Setup: Configure your webhook URL in the Members Area → API Keys. A single URL receives both incoming messages and delivery reports.

How It Works

Your webhook receives POST requests with JSON payloads. Check the type field to determine which event occurred:

  • "type": "SMS" - Incoming SMS message
  • "type": "dlr" - Delivery status report

Incoming SMS Payload

{
  "type": "SMS",
  "messageId": "987654",
  "connexusMessageId": "MO6a1b2c3d4e5f",
  "from": "+6421234567",
  "to": "shortcode",
  "body": "Reply text",
  "timestamp": 1234567890,
  "encoding": "text/plain",
  "network": "carrier_name"
}

Replies to an outbound message also include replyTo and replyToConnexusId referencing the original message, if available. If you supplied a custom messageId when sending the original message, the reply also includes it as replyToCustomerMessageId.

Delivery Report Payload

{
  "type": "dlr",
  "messageId": "987654",
  "connexusMessageId": "MSG6a1b2c3d4e5f",
  "status": "DELIVRD",
  "statusCode": 1,
  "timestamp": 1234567890,
  "details": {
    "smsc": "carrier_name",
    "smscid": "abc12345-6789-def0-1234-56789abcdef0"
  }
}

If you supplied a custom messageId when sending, the report also includes it as customerMessageId.

Delivery Status Codes

statusCode status Description
1DELIVRDMessage delivered successfully
2UNDELIVMessage undeliverable
4QUEUEDMessage expired/queued
8ACCEPTDMessage accepted by network
16UNDELIVMessage undeliverable
-1BLOCKEDMessage blocked before carrier submission. The report includes an additional blockedReason field: unsubscribed (recipient has opted out) or invalid_number. This is a terminal status — no further delivery reports will follow.

Example Webhook Handlers

<?php
// webhook.php - handles both incoming SMS and delivery reports
$data = json_decode(file_get_contents('php://input'), true);

if (($data['type'] ?? '') === 'SMS') {
    // Incoming SMS message
    $from = $data['from'];
    $message = $data['body'];
    error_log("Incoming SMS from {$from}: {$message}");

    // Optionally send a reply
    // sendReply($from, "Thanks for your message!");

} elseif (($data['type'] ?? '') === 'dlr') {
    // Delivery status report
    $messageId = $data['messageId'];
    $status = $data['status'];

    if ($status === 'DELIVRD') {
        error_log("Message {$messageId} delivered successfully");
    } elseif ($status === 'UNDELIV') {
        error_log("Message {$messageId} failed to deliver");
    }
}

// Always return 200 OK
http_response_code(200);
echo json_encode(['success' => true]);
?>
const express = require('express');
const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
  const data = req.body;

  if (data.type === 'SMS') {
    // Incoming SMS
    console.log(`SMS from ${data.from}: ${data.body}`);
  } else if (data.type === 'dlr') {
    // Delivery report
    console.log(`Message ${data.messageId}: ${data.status}`);
  }

  res.json({ success: true });
});

app.listen(3000);
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    data = request.json

    if data.get('type') == 'SMS':
        # Incoming SMS
        print(f"SMS from {data['from']}: {data['body']}")
    elif data.get('type') == 'dlr':
        # Delivery report
        print(f"Message {data['messageId']}: {data['status']}")

    return jsonify({'success': True})

if __name__ == '__main__':
    app.run(port=3000)
Requirements:
  • Webhook URL must use HTTPS
  • Should respond with HTTP 200 within 5 seconds
  • Failed webhooks are retried up to 3 times

Query received mobile-originated messages with filters and pagination:

Using API Key Token (Recommended)
curl -X POST https://api.websms.co.nz/api/connexus/mo/query \
  -H "Authorization: Bearer wst_your_token..." \
  -d "from=6421234567" \
  -d "to=551" \
  -d "start_date=2025-01-01" \
  -d "end_date=2025-01-31" \
  -d "limit=100" \
  -d "page=1"
Using Username/Password
curl -X POST https://api.websms.co.nz/api/connexus/mo/query \
  -d "userId=user@domain.co.nz" \
  -d "password=yourpassword" \
  -d "from=6421234567" \
  -d "to=551" \
  -d "start_date=2025-01-01" \
  -d "end_date=2025-01-31" \
  -d "limit=100" \
  -d "page=1"

Query Parameters:

Parameter Required Description Example
userId Required Your WebSMS account email user@domain.co.nz
password Required Your WebSMS account password yourpassword
from Optional Filter by sender number 6421234567
to Optional Filter by recipient (shortcode) 551
callback_status Optional Filter by webhook callback HTTP status 200
start_date Optional Start of date range (Y-m-d or Y-m-d H:i:s) 2025-01-01
end_date Optional End of date range (Y-m-d or Y-m-d H:i:s) 2025-01-31
limit Optional Number of results per page (default: 100, max: 200) 100
page Optional Page number for pagination (default: 1) 1

Response Format:

{
  "status": "success",
  "messages": [
    {
      "messageId": "1234",
      "from": "6421234567",
      "to": "551",
      "body": "Reply message text",
      "receivedTime": "2025-01-15 14:30:00",
      "timestamp": 1736948400,
      "relatedMessageId": "5678",        // Optional: only if reply to sent message
      "callbackUrl": "https://...",      // Optional: only if webhook configured
      "callbackStatus": "200",           // Optional: only if webhook called
      "callbackResponse": "OK"           // Optional: only if webhook responded
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 100,
    "totalRecords": 250,
    "totalPages": 3,
    "hasNextPage": true,
    "hasPreviousPage": false
  },
  "filters": {
    "from": "6421234567",
    "to": "551",
    "callback_status": null,
    "start_date": "2025-01-01 00:00:00",
    "end_date": "2025-01-31 23:59:59"
  }
}
Note: Fields like relatedMessageId, callbackUrl, callbackStatus, and callbackResponse only appear in the response if they have values.

Example Implementation (PHP):

<?php
// Query incoming messages
$ch = curl_init('https://api.websms.co.nz/api/connexus/mo/query');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'userId' => 'user@domain.co.nz',
    'password' => 'yourpassword',
    'from' => '6421234567',
    'start_date' => '2025-01-01',
    'end_date' => '2025-01-31',
    'limit' => 100,
    'page' => 1
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode == 200) {
    $data = json_decode($response, true);

    echo "Total messages: " . $data['pagination']['totalRecords'] . "\n";

    foreach ($data['messages'] as $msg) {
        echo "From: {$msg['from']}, Message: {$msg['body']}\n";
    }

    // Fetch next page if available
    if ($data['pagination']['hasNextPage']) {
        // Query with page=2
    }
} else {
    echo "Error: HTTP $httpCode";
}
?>

Look up delivery status of sent messages:

Three ways to query. Looking up by message ID works for messages of any age; browsing with filters is limited to the current calendar month.

By our message ID (from the send response, up to 100 per call)
curl -X POST https://api.websms.co.nz/api/connexus/sms/status/query \
  -H "Authorization: Bearer wst_your_token..." \
  -d "message_id=1188236,1188240"
By your own messageId (supplied when sending)
curl -X POST https://api.websms.co.nz/api/connexus/sms/status/query \
  -H "Authorization: Bearer wst_your_token..." \
  -d "messageId=PRAC-042-7731"
Browse the current month with filters
curl -X POST https://api.websms.co.nz/api/connexus/sms/status/query \
  -H "Authorization: Bearer wst_your_token..." \
  -d "status=UNDELIV" \
  -d "start_date=2026-08-01 09:00:00" \
  -d "end_date=2026-08-30" \
  -d "limit=50" \
  -d "page=1"

Optional filters: to (recipient number), status, start_date/end_date (date or date-time, current calendar month only). Max 100 results per page.

Response
{
  "status": "success",
  "messages": [
    {
      "message_id": "1188236",
      "to": "6421234567",
      "from": "2190",
      "status": "DELIVRD",
      "statusCode": 1,
      "sentTime": "2026-08-30 10:15:22",
      "statusTime": "2026-08-30 10:15:31",
      "customerMessageId": "PRAC-042-7731"
    }
  ],
  "notFound": []
}

Note: status uses the same values as the delivery report webhook (see the status codes table above). PENDING with statusCode: null means no delivery report has been received yet. customerMessageId, rateCode, statusTime and blockedReason only appear when they have values. Username/password authentication (userId/password) is also supported.

Reconcile monthly billing, grouped by rateCode:

If you send on behalf of your own customers, tag each send with a rateCode (e.g. your customer number) and use this endpoint at the end of the month to see exactly what each rateCode was billed. Reports actual billed transactions, so multi-part messages are counted in segments and the amounts always reconcile with your WebSMS account balance.

Monthly summary, one row per rateCode
curl -X POST https://api.websms.co.nz/api/connexus/billing/query \
  -H "Authorization: Bearer wst_your_token..." \
  -d "month=2026-08"

Optional parameters: month (Y-m, defaults to the current month) or start_date/end_date (max 366 days); rateCode to report a single code — a base code also matches its -OTP/-APPT variants, so filtering on your customer number returns that customer's full spend (use untagged for messages sent without one); detail=1 for individual transactions with limit/page pagination (max 500 per page).

Response
{
  "status": "success",
  "rateCodes": [
    {
      "rateCode": "CUST-0042",
      "sendType": "sms",
      "messages": 1250,
      "segments": 1311,
      "amount": 91.77,
      "gst": 13.7655,
      "totalInclGst": 105.5355
    },
    {
      "rateCode": "CUST-0042-OTP",
      "sendType": "otp",
      "baseRateCode": "CUST-0042",
      "messages": 340,
      "segments": 340,
      "amount": 27.20,
      "gst": 4.08,
      "totalInclGst": 31.28
    },
    {
      "rateCode": "CUST-0042-APPT",
      "sendType": "appointment",
      "baseRateCode": "CUST-0042",
      "messages": 95,
      "segments": 102,
      "amount": 8.16,
      "gst": 1.224,
      "totalInclGst": 9.384
    },
    {
      "rateCode": "untagged",
      "sendType": "sms",
      "messages": 18,
      "segments": 18,
      "amount": 1.26,
      "gst": 0.189,
      "totalInclGst": 1.449
    }
  ],
  "totals": {
    "messages": 1703,
    "segments": 1771,
    "amount": 128.39,
    "gst": 19.2585,
    "totalInclGst": 147.6485
  },
  "filters": {
    "rateCode": null,
    "start_date": "2026-08-01 00:00:00",
    "end_date": "2026-08-31 23:59:59"
  }
}

Note: amounts are in NZD exclusive of GST, with GST reported separately (zero for non-NZ accounts). OTP and appointment sends are billed at their own rates, so they always report separately: with a rateCode supplied they appear as {rateCode}-OTP / {rateCode}-APPT (giving you up to three lines per customer), otherwise under the fixed codes OTP and APPT. Messages blocked before carrier submission are billed and appear under their rateCode. Billing detail is recorded from August 2026 onwards. Username/password authentication (userId/password) is also supported.

Fetch numbers that have opted out (STOP/END) from your account:

The same list as Members Area » Blocked Numbers. Use since to poll for changes only — a repeat STOP refreshes blockedAt, and with include=all resubscribed numbers come back with active: false so you can unblock them on your side too.

Dedicated shortcode with an MO webhook? This list stays empty — inbound messages on your own shortcode are passed straight to your webhook and are not scanned for opt-out keywords, so opt-out handling is your responsibility. See Sending Hours & Compliance.
Delta sync (changes since a date/time)
curl -X POST https://api.websms.co.nz/api/connexus/unsubscribes/query \
  -H "Authorization: Bearer wst_your_token..." \
  -d "since=2026-08-30 00:00:00" \
  -d "include=all"

Omit since for the full current list. Optional: limit (default 100, max 200), page. Username/password auth also supported.

Response
{
  "status": "success",
  "numbers": [
    {
      "number": "6421234567",
      "keyword": "STOP",
      "blockedAt": "2026-08-30 14:22:10",
      "active": true,
      "shortcode": "2190"
    },
    {
      "number": "6427654321",
      "keyword": "STOP",
      "blockedAt": "2026-08-27 09:15:02",
      "active": false,
      "resubscribedAt": "2026-08-30 09:01:44"
    }
  ],
  "pagination": { "page": 1, "limit": 100, "totalRecords": 2, "totalPages": 1 }
}

Query your account balance before sending messages:

Using API Key Token (Recommended)
curl -X POST https://api.websms.co.nz/api/connexus/sms/balance \
  -H "Authorization: Bearer wst_your_token..."
Using Username/Password
curl -X POST https://api.websms.co.nz/api/connexus/sms/balance \
  -d "userId=user@domain.co.nz" \
  -d "password=yourpassword"

Response Format:

{
  "balance": "125.50",
  "currency": "NZD"
}
Note: The balance endpoint only requires authentication credentials. No message parameters are needed.

Example Implementation (PHP):

<?php
// Check account balance
$ch = curl_init('https://api.websms.co.nz/api/connexus/sms/balance');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'userId' => 'user@domain.co.nz',
    'password' => 'yourpassword'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode == 200) {
    $data = json_decode($response, true);
    echo "Current balance: $" . $data['balance'] . " " . $data['currency'];
} else {
    echo "Error: HTTP $httpCode";
}
?>

Send a one-time verification code for 2FA authentication:

Full working examples available to download in PHP, Node.js, Python and Go - including a runnable end-to-end demo with WebOTP autofill: Send SMS OTP / 2FA codes via the WebSMS API →
Using API Key Token (Recommended)
curl -X POST https://api.websms.co.nz/api/connexus/sms/otp \
  -H "Authorization: Bearer wst_your_token..." \
  -d "to=6421234567" \
  -d "msgCompany=MyApp"
Using Username/Password
curl -X POST https://api.websms.co.nz/api/connexus/sms/otp \
  -d "userId=user@domain.co.nz" \
  -d "password=yourpassword" \
  -d "to=6421234567" \
  -d "msgCompany=MyApp"
Auto-generated code: If you don't provide a code, a secure 6-digit code will be generated automatically and returned in the response.
Special pricing: OTP messages to New Zealand and Australia are charged at $0.08 + GST per SMS (or your custom rate if lower). OTP messages to other destinations are charged at international rates — see International Rates.
Fraud warning: If your OTP form is publicly accessible, protect it against abuse (rate limiting, CAPTCHA, restricting destination countries). Attackers can drive OTP requests to premium international numbers they control. International OTP sends are billed at international rates and are your responsibility.

Parameters:

Parameter Required Description Example
userId Required Your WebSMS account email user@domain.co.nz
password Required Your WebSMS account password yourpassword
to Required Recipient phone number 6421234567
msgCompany Required Your company/app name (shown in message) MyApp
msgCode Optional Custom 4-8 digit code (auto-generated if not provided) 426817
msgComment Optional Additional text appended to message (truncated to fit 160 chars) Valid for 5 minutes.
from Optional Sender ID (default: 552) 552
webOtpDomain Optional Bare hostname (example.com or app.example.com) to append as a WebOTP API autofill marker on the SMS's last line (format: @host #code). Lets Chrome/Safari offer one-tap autofill into a matching <input autocomplete="one-time-code"> on your origin. Schemes/paths/spaces are rejected - pass the host only. app.example.com
rateCode Optional Billing tag (e.g. your own customer number, max 95 chars). Reported in the billing report as {rateCode}-OTP; sends without one report under the fixed code OTP. CUST-0042
sandbox Optional If true, returns a demo response without sending or billing. Useful for integration testing. true

Message Format:

{msgCode} is your {msgCompany} verification code{msgComment}
[\n@{webOtpDomain} #{msgCode}]    ← only if webOtpDomain is supplied

Examples:
- "426817 is your MyApp verification code"
- "426817 is your MyApp verification code. Valid for 5 minutes."
- "426817 is your MyApp verification code. Valid for 5 minutes.
   @example.com #426817"

Response Format:

{
  "status": "success",
  "message_id": "abc123def456",
  "to": "6421234567",
  "from": "552",
  "code": "426817",      // The OTP code that was sent (store this to verify later!)
  "parts": 1
}
Important: The code field is always returned in the response. You must store this code on your server to verify the user's input later. The code is not stored by WebSMS.

Example Implementation:

<?php
// Send OTP code
function sendOTP($phone, $company) {
    $ch = curl_init('https://api.websms.co.nz/api/connexus/sms/otp');
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
        'userId' => 'user@domain.co.nz',
        'password' => 'yourpassword',
        'to' => $phone,
        'msgCompany' => $company,
        'msgComment' => 'Valid for 5 minutes.'
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    $data = json_decode($response, true);

    // Store the code for verification
    $_SESSION['otp_code'] = $data['code'];
    $_SESSION['otp_expires'] = time() + 300; // 5 minutes

    return $data;
}

// Verify OTP code
function verifyOTP($userInput) {
    if (time() > $_SESSION['otp_expires']) {
        return false; // Expired
    }
    return $userInput === $_SESSION['otp_code'];
}

// Usage
$result = sendOTP('6421234567', 'MyApp');
echo "OTP sent! Code: " . $result['code'];
?>
const axios = require('axios');

// In-memory store (use Redis/DB in production)
const otpStore = new Map();

async function sendOTP(phone, company) {
  const response = await axios.post('https://api.websms.co.nz/api/connexus/sms/otp', {
    userId: 'user@domain.co.nz',
    password: 'yourpassword',
    to: phone,
    msgCompany: company,
    msgComment: 'Valid for 5 minutes.'
  });

  // Store code for verification
  otpStore.set(phone, {
    code: response.data.code,
    expires: Date.now() + 300000 // 5 minutes
  });

  return response.data;
}

function verifyOTP(phone, userInput) {
  const stored = otpStore.get(phone);
  if (!stored || Date.now() > stored.expires) return false;
  if (userInput === stored.code) {
    otpStore.delete(phone);
    return true;
  }
  return false;
}

// Usage
const result = await sendOTP('6421234567', 'MyApp');
console.log('OTP sent! Code:', result.code);
import requests
import time

# In-memory store (use Redis/DB in production)
otp_store = {}

def send_otp(phone, company):
    response = requests.post('https://api.websms.co.nz/api/connexus/sms/otp', data={
        'userId': 'user@domain.co.nz',
        'password': 'yourpassword',
        'to': phone,
        'msgCompany': company,
        'msgComment': 'Valid for 5 minutes.'
    })
    data = response.json()

    # Store code for verification
    otp_store[phone] = {
        'code': data['code'],
        'expires': time.time() + 300  # 5 minutes
    }

    return data

def verify_otp(phone, user_input):
    stored = otp_store.get(phone)
    if not stored or time.time() > stored['expires']:
        return False
    if user_input == stored['code']:
        del otp_store[phone]
        return True
    return False

# Usage
result = send_otp('6421234567', 'MyApp')
print(f"OTP sent! Code: {result['code']}")
using System.Net.Http;
using System.Text.Json;

public class OtpService
{
    private static Dictionary<string, (string code, DateTime expires)> _store = new();
    private readonly HttpClient _http = new();

    public async Task<string> SendOtpAsync(string phone, string company)
    {
        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("userId", "user@domain.co.nz"),
            new KeyValuePair<string, string>("password", "yourpassword"),
            new KeyValuePair<string, string>("to", phone),
            new KeyValuePair<string, string>("msgCompany", company),
            new KeyValuePair<string, string>("msgComment", "Valid for 5 minutes.")
        });

        var response = await _http.PostAsync(
            "https://api.websms.co.nz/api/connexus/sms/otp", content);
        var json = await response.Content.ReadAsStringAsync();
        var data = JsonSerializer.Deserialize<JsonElement>(json);
        var code = data.GetProperty("code").GetString();

        // Store code for verification
        _store[phone] = (code, DateTime.UtcNow.AddMinutes(5));

        return code;
    }

    public bool VerifyOtp(string phone, string userInput)
    {
        if (!_store.TryGetValue(phone, out var stored)) return false;
        if (DateTime.UtcNow > stored.expires) return false;
        if (userInput == stored.code)
        {
            _store.Remove(phone);
            return true;
        }
        return false;
    }
}

// Usage
var otp = new OtpService();
var code = await otp.SendOtpAsync("6421234567", "MyApp");
Console.WriteLine($"OTP sent! Code: {code}");

Send appointment reminder SMS with dynamic fields:

Using API Key Token (Recommended)
curl -X POST https://api.websms.co.nz/api/connexus/sms/appointment \
  -H "Authorization: Bearer wst_your_token..." \
  -d "to=6421234567" \
  -d "msgCompany=Sunrise Wellness Clinic" \
  -d "name=Barry" \
  -d "date=28/10/25" \
  -d "time=11:15am" \
  -d "replyY=true" \
  -d "callUs=09-555-1234" \
  -d "address=42 Main Street, Auckland"
Minimal Example (required fields only)
curl -X POST https://api.websms.co.nz/api/connexus/sms/appointment \
  -H "Authorization: Bearer wst_your_token..." \
  -d "to=6421234567" \
  -d "msgCompany=Sunrise Wellness Clinic"
Smart templating: Only fields you provide are included in the message. Omit any optional field to leave it out of the SMS.
Special pricing: Templated messages are charged at $0.08 + GST per SMS part (or your custom rate if lower).

Parameters:

Parameter Required Description Example
to Required Recipient phone number 6421234567
msgCompany Required Your business/company name Sunrise Wellness Clinic
name Optional Customer's first name (adds "Hi {name}, " prefix) Barry
date Optional Appointment date 28/10/25
time Optional Appointment time 11:15am
replyY Optional If true, adds "Reply Y to confirm" true
callUs Optional Phone number to call for rebooking 09-555-1234
address Optional Business address 42 Main Street, Auckland
from Optional Sender ID (default: your account default) 552
rateCode Optional Billing tag (e.g. your own customer number, max 95 chars). Reported in the billing report as {rateCode}-APPT; sends without one report under the fixed code APPT. CUST-0042
sandbox Optional If true, returns a demo response without sending or billing. Useful for integration testing. true

Message Format Examples:

// All fields provided:
Hi Barry, your next appointment with Sunrise Wellness Clinic is on 28/10/25 at 11:15am. Reply Y to confirm or Pls call us on 09-555-1234 to re-book. Our Address: 42 Main Street, Auckland. Std SMS charges apply.

// Without name:
Your next appointment with Sunrise Wellness Clinic is on 28/10/25 at 11:15am. Reply Y to confirm or Pls call us on 09-555-1234 to re-book. Our Address: 42 Main Street, Auckland. Std SMS charges apply.

// Without date/time:
Hi Barry, your next appointment with Sunrise Wellness Clinic is coming up. Reply Y to confirm or Pls call us on 09-555-1234 to re-book. Std SMS charges apply.

// Minimal (company only):
Your next appointment with Sunrise Wellness Clinic is coming up. Std SMS charges apply.

// Without replyY:
Hi Barry, your next appointment with Sunrise Wellness Clinic is on 28/10/25 at 11:15am. Pls call us on 09-555-1234 to re-book. Our Address: 42 Main Street, Auckland. Std SMS charges apply.

Response Format:

{
  "status": "success",
  "message_id": "abc123def456",
  "to": "6421234567",
  "from": "552",
  "message": "Hi Barry, your next appointment with Sunrise Wellness Clinic is on 28/10/25 at 11:15am. Reply Y to confirm or Pls call us on 09-555-1234 to re-book. Our Address: 42 Main Street, Auckland. Std SMS charges apply.",
  "parts": 2
}

Example Implementation (PHP):

<?php
function sendAppointmentReminder($phone, $company, $options = []) {
    $params = [
        'to' => $phone,
        'msgCompany' => $company
    ];

    // Add optional fields if provided
    if (!empty($options['name'])) $params['name'] = $options['name'];
    if (!empty($options['date'])) $params['date'] = $options['date'];
    if (!empty($options['time'])) $params['time'] = $options['time'];
    if (!empty($options['replyY'])) $params['replyY'] = 'true';
    if (!empty($options['callUs'])) $params['callUs'] = $options['callUs'];
    if (!empty($options['address'])) $params['address'] = $options['address'];

    $ch = curl_init('https://api.websms.co.nz/api/connexus/sms/appointment');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer wst_your_token...']);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return json_decode($response, true);
}

// Usage examples:
// Full appointment reminder
$result = sendAppointmentReminder('6421234567', 'Sunrise Wellness Clinic', [
    'name' => 'Barry',
    'date' => '28/10/25',
    'time' => '11:15am',
    'replyY' => true,
    'callUs' => '09-555-1234',
    'address' => '42 Main Street, Auckland'
]);

// Minimal reminder
$result = sendAppointmentReminder('6421234567', 'Sunrise Wellness Clinic');

// With name and date only
$result = sendAppointmentReminder('6421234567', 'Sunrise Wellness Clinic', [
    'name' => 'Barry',
    'date' => '28/10/25',
    'time' => '11:15am'
]);
?>

Validate NZ mobile numbers and check carrier portability status:

Using API Key Token (Recommended)
curl -X POST https://api.websms.co.nz/api/connexus/number/lookup \
  -H "Authorization: Bearer wst_your_token..." \
  -d "number=0211234567"
Using Username/Password
curl -X POST https://api.websms.co.nz/api/connexus/number/lookup \
  -d "userId=user@domain.co.nz" \
  -d "password=yourpassword" \
  -d "number=0211234567"
Pricing: $0.005 per lookup + GST. Real-time data from the IPMS (Industry Portability Management System).

Parameters:

Parameter Required Description Example
userId Required Your WebSMS account email user@domain.co.nz
password Required Your WebSMS account password yourpassword
number Required NZ mobile number (local 02x or international 642x format) 0211234567 or 6421234567

Success Response:

{
  "success": true,
  "number": "6421234567",
  "carrier": "Spark",
  "ported": false,
  "original_network": "Spark",
  "current_network": "Spark",
  "network_code": "TCNZ"
}

Error Responses (400 Bad Request):

// Number too short
{
  "success": false,
  "error": "Invalid number",
  "message": "Number too short. NZ mobile numbers must be 9-11 digits (e.g., 021234567)."
}

// Number too long
{
  "success": false,
  "error": "Invalid number",
  "message": "Number too long. NZ mobile numbers must be 9-11 digits (e.g., 021234567)."
}

// Invalid prefix
{
  "success": false,
  "error": "Invalid number",
  "message": "Invalid prefix. NZ mobile numbers must start with 02X (e.g., 021, 022, 027)."
}

// Invalid format
{
  "success": false,
  "error": "Invalid number",
  "message": "Invalid format. NZ mobile numbers must start with 02X (local) or 642X (international)."
}

Response Fields:

Field Description
successWhether the lookup was successful
numberNormalized phone number (international format)
carrierCurrent carrier name (Spark, One NZ, 2degrees, Skinny, etc.)
portedWhether the number has been ported from its original network
original_networkOriginal network based on number prefix
current_networkCurrent network servicing the number
network_codeNetwork operator code (TCNZ, VNZL, TNZD, etc.)

Example Implementation (PHP):

<?php
// Lookup a phone number
$ch = curl_init('https://api.websms.co.nz/api/connexus/number/lookup');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'userId' => 'user@domain.co.nz',
    'password' => 'yourpassword',
    'number' => '0211234567'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode == 200) {
    $data = json_decode($response, true);

    if ($data['success']) {
        echo "Number: " . $data['number'] . "\n";
        echo "Carrier: " . $data['carrier'] . "\n";
        echo "Ported: " . ($data['ported'] ? 'Yes' : 'No') . "\n";
    }
} else {
    echo "Error: HTTP $httpCode";
}
?>
Use Cases: SMS routing optimization, contact database cleaning, fraud prevention, carrier-based cost management, customer verification.

Alternative method to configure all webhooks at once:

Using API Key Token (Recommended)
# Get current configuration
curl -X GET https://api.websms.co.nz/api/connexus/configure \
  -H "Authorization: Bearer wst_your_token..."

# Set webhook URL (receives both incoming SMS and delivery reports)
curl -X POST https://api.websms.co.nz/api/connexus/configure \
  -H "Authorization: Bearer wst_your_token..." \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://yoursite.com/webhook"
  }'
Using Username/Password
# Get current configuration
curl -X GET https://api.websms.co.nz/api/connexus/configure \
  -u "user@domain.co.nz:password"

# Set webhook URL (receives both incoming SMS and delivery reports)
curl -X POST https://api.websms.co.nz/api/connexus/configure \
  -u "user@domain.co.nz:password" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://yoursite.com/webhook"
  }'
Webhook Payloads: Your webhook receives both incoming SMS and delivery reports. Check the type field: "SMS" for incoming SMS, "dlr" for delivery reports. See the Webhooks section for full payload details and examples.

Using API Keys (Recommended)
<?php
// Step 1: Get access token
$ch = curl_init('https://api.websms.co.nz/api/connexus/auth/token');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'client_id' => 'cid_your_client_id',
    'client_secret' => 'csk_your_client_secret'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$tokenResponse = json_decode(curl_exec($ch), true);
curl_close($ch);

$accessToken = $tokenResponse['access_token'];

// Step 2: Send SMS with token
$ch = curl_init('https://api.websms.co.nz/api/connexus/sms/out');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $accessToken
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'to' => '6421234567',
    'body' => 'Hello from WebSMS!'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $httpCode == 200 ? "Sent!" : "Error: $httpCode";
?>
Using Username/Password
<?php
$url = 'https://api.websms.co.nz/api/connexus/sms/out';
$data = [
    'userId' => 'user@domain.co.nz',
    'password' => 'yourpassword',
    'to' => '6421234567',
    'body' => 'Hello from WebSMS!'
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode == 204) {
    echo "Message sent successfully!";
} else {
    echo "Error: HTTP $httpCode";
}
?>
Using API Keys (Recommended)
import requests

# Step 1: Get access token
token_response = requests.post(
    'https://api.websms.co.nz/api/connexus/auth/token',
    data={
        'client_id': 'cid_your_client_id',
        'client_secret': 'csk_your_client_secret'
    }
)
access_token = token_response.json()['access_token']

# Step 2: Send SMS with token
response = requests.post(
    'https://api.websms.co.nz/api/connexus/sms/out',
    headers={'Authorization': f'Bearer {access_token}'},
    data={'to': '6421234567', 'body': 'Hello from WebSMS!'}
)

print("Sent!" if response.status_code == 200 else f"Error: {response.status_code}")
Using Username/Password
import requests

url = 'https://api.websms.co.nz/api/connexus/sms/out'
data = {
    'userId': 'user@domain.co.nz',
    'password': 'yourpassword',
    'to': '6421234567',
    'body': 'Hello from WebSMS!'
}

response = requests.post(url, data=data)

if response.status_code == 204:
    print("Message sent successfully!")
else:
    print(f"Error: HTTP {response.status_code}")
Using API Keys (Recommended)
const axios = require('axios');

async function sendSMS() {
    // Step 1: Get access token
    const tokenRes = await axios.post(
        'https://api.websms.co.nz/api/connexus/auth/token',
        new URLSearchParams({
            client_id: 'cid_your_client_id',
            client_secret: 'csk_your_client_secret'
        })
    );

    // Step 2: Send SMS with token
    const smsRes = await axios.post(
        'https://api.websms.co.nz/api/connexus/sms/out',
        new URLSearchParams({ to: '6421234567', body: 'Hello!' }),
        { headers: { Authorization: `Bearer ${tokenRes.data.access_token}` }}
    );

    console.log('Sent!');
}

sendSMS().catch(err => console.error('Error:', err.response?.status));
Using Username/Password
const axios = require('axios');

const data = new URLSearchParams({
    userId: 'user@domain.co.nz',
    password: 'yourpassword',
    to: '6421234567',
    body: 'Hello from WebSMS!'
});

axios.post('https://api.websms.co.nz/api/connexus/sms/out', data)
    .then(response => {
        if (response.status === 204) {
            console.log('Message sent successfully!');
        }
    })
    .catch(error => {
        console.error('Error:', error.response?.status);
    });
Using API Keys (Recommended)
using System.Net.Http;
using System.Text.Json;

var client = new HttpClient();

// Step 1: Get access token
var tokenContent = new FormUrlEncodedContent(new Dictionary<string, string>
{
    { "client_id", "cid_your_client_id" },
    { "client_secret", "csk_your_client_secret" }
});
var tokenRes = await client.PostAsync(
    "https://api.websms.co.nz/api/connexus/auth/token", tokenContent);
var tokenJson = JsonDocument.Parse(await tokenRes.Content.ReadAsStringAsync());
var accessToken = tokenJson.RootElement.GetProperty("access_token").GetString();

// Step 2: Send SMS with token
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
var smsContent = new FormUrlEncodedContent(new Dictionary<string, string>
{
    { "to", "6421234567" },
    { "body", "Hello from WebSMS!" }
});
var smsRes = await client.PostAsync(
    "https://api.websms.co.nz/api/connexus/sms/out", smsContent);

Console.WriteLine(smsRes.IsSuccessStatusCode ? "Sent!" : $"Error: {smsRes.StatusCode}");
Using Username/Password
using System;
using System.Net.Http;
using System.Collections.Generic;

var client = new HttpClient();
var values = new Dictionary<string, string>
{
    { "userId", "user@domain.co.nz" },
    { "password", "yourpassword" },
    { "to", "6421234567" },
    { "body", "Hello from WebSMS!" }
};

var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync(
    "https://api.websms.co.nz/api/connexus/sms/out",
    content
);

if ((int)response.StatusCode == 204)
{
    Console.WriteLine("Message sent successfully!");
}
else
{
    Console.WriteLine($"Error: {response.StatusCode}");
}

API Support

Our team is here to help with your integration:

  • Free integration assistance
  • Testing credits available
  • Direct technical support

Contact Us

Email: support@websms.co.nz
Phone: +64 27 4909-712
Hours: Monday-Friday, 9am-5pm NZST

If you're moving from Bulletin Connect, switching to WebSMS is straightforward. Our Connexus API is fully compatible - just update three things:

OLD

service.bulletinconnect.net

NEW

websms.co.nz
  1. Username: Your WebSMS email
  2. Password: Your WebSMS password
  3. URL: Change domain only
100% Compatible - No code changes required beyond updating your configuration

Endpoint Mapping

Function Bulletin Connect WebSMS
Send SMS http://service.bulletinconnect.net/api/1/sms/out https://api.websms.co.nz/api/connexus/sms/out
Receive SMS http://service.bulletinconnect.net/api/1/sms/in https://api.websms.co.nz/api/connexus/sms/in
Status Updates http://service.bulletinconnect.net/api/1/sms/status https://api.websms.co.nz/api/connexus/sms/status

Need help with migration? Contact us at support@websms.co.nz - we offer free migration assistance.