How Hong Kong SMS billing works — characters, Unicode and segmentation
“I sent to 1,000 numbers. Why does the bill say 3,000 messages?”
It is the billing question we field most, and the answer is nearly always the same: one message is not one billing unit. Chinese content makes this especially easy to trip over.
Here are the rules, so you can work it out yourself.
Two encodings, two limits
The SMS standard was set early, before anyone was thinking about Chinese. It carries two text encodings:
GSM-7 — 7 bits per character, covering Latin letters, digits and common punctuation. UCS-2 — 16 bits per character, able to represent Chinese, Japanese, Korean, emoji and the rest.
A single message body holds a fixed 140 bytes (1,120 bits). Divide it out:
| Encoding | Single message | Per segment when split |
|---|---|---|
| GSM-7 (Latin only) | 160 characters | 153 characters |
| UCS-2 (any Chinese) | 70 characters | 67 characters |
Why does the limit drop once it splits? Each segment gives up 6 bytes to a header (UDH) carrying “segment n of m”, so the handset can reassemble them.
The critical part: a single UCS-2 character forces the entire message into UCS-2. One Chinese character, one emoji, one full-width punctuation mark, and the limit falls from 160 straight to 70.
What that means for real messages
Take a typical promotional message. Around seventy-odd characters of Chinese sits right on the edge of the first segment — two more characters and it becomes two segments and the cost doubles.
And seventy Chinese characters strikes most people as impossibly short. That is exactly why Chinese promotional copy is written so tightly: every character costs money.
Practical ways to save characters
- Short links. A full UTM-tagged URL can eat 60 characters; a short link gets that to around 20. Our trackable links are short by design.
- Numerals for dates. “by 8/31” is half the length of the written-out form.
- Keep the unsubscribe information. This is a UEMO requirement and is not something to cut for length — see the compliance checklist.
- Do not add an emoji for decoration. One emoji in an otherwise Latin message drops the limit from 160 to 70.
The hidden double-width characters in GSM-7
Even in a pure Latin message, a handful of characters occupy two slots:
^ { } [ ] ~ | \ €
They live in the GSM-7 extension set and need an escape character. A few [ and ] in a template can push 158 characters of content past 160 and into a second segment.
Worth remembering when your placeholders are square-bracketed.
Reconciling what you were actually billed
Do not guess. The API has a usage endpoint:
GET /v1/sms/get-usage-report?date_from=2026-07-01&date_to=2026-07-31
which returns:
{
"rc": 100,
"rm": "OK",
"date_from": "2026-07-01",
"date_to": "2026-07-31",
"sms_count": "30000",
"points_used": "5000"
}
sms_count is the number of billed messages, points_used the points consumed. Divide against your own send records and you get the average segments per recipient.
If that ratio is meaningfully above 1, your template is segmenting — go back and tighten the copy.
Which tier billing attaches to
Commonly misunderstood. In our model, billing happens when the 1st callback returns SMD — the moment the message is successfully submitted to the carrier.
Conversely, the -100 family of first-tier failures (IM bad number, OFCA on the register, IP no points, IS unregistered sender) are not billed.
A 2nd-tier DLR of UNDELIV or EXPIRED is a different matter: the message did reach the carrier, so that cost is already incurred. Which is why list hygiene pays — the real cost of a dirty list is not the rejections, it is the messages that went out and could never land.
Every status code is set out in DLR status codes explained.
Three recommendations
1. Count length before sending. Most languages have an off-the-shelf SMS segmentation library. Showing “this will split into n segments” when a template is saved in your CMS is far more useful than reading it off an invoice later.
2. Monitor the points balance. Call /v1/acct/get-points-balance before a large batch. Running out mid-send fails the remainder with IP.
3. List quality beats character counting. Cutting a message from two segments to one halves that message’s cost, but removing 20% invalid numbers removes cost you were spending on nothing at all — and does not compromise the message.
Endpoint detail is in the SMS API integration guide. For actual rates at your volume, get in touch.