On-demand premium SMS
On-demand premium SMS lets you charge subscribers per-message for content they request — quizzes, sports scores, news clips, jokes, horoscopes. The customer texts a keyword to your shortcode, you reply with the paid content, and the carrier collects the fee on your behalf.
- How the flow works
- Request parameters
- Response parameters
- Sample request
- Incoming keyword webhook
- Delivery status webhook
- Error responses
How the flow works
- The subscriber texts your keyword (e.g.
GOAL) to your shortcode. - Sozuri pushes an incoming webhook to your application with a
linkIdidentifying this request. - You reply via the API, passing the same
linkIdback — this is what triggers the premium charge. - Sozuri sends a delivery webhook when the premium SMS reaches the subscriber and the carrier confirms the charge.
Required headers
POST /api/v1/messaging/sendpremium HTTP/1.1
Host: sozuri.net
Authorization: Bearer Your_Project_API_KEY
Content-Type: application/json
Accept: application/json
Request parameters
| Field | Required | Type | Description |
|---|---|---|---|
| project | Yes | String | The Sozuri project that owns the API key. |
| from | Yes | String | Your premium shortcode (e.g. 23546). |
| number | Yes | String | The subscriber’s phone number. 0722-503-129, +254722503129 and 722503129 all normalise to 254722503129. Anything that is not a Kenyan mobile number is refused with Unsupported Number. |
| channel | No | String | Accepted and ignored. Kept for compatibility with older integrations. |
| type | No | String | Accepted and ignored — the product type comes from the offer itself, not the request. |
| message | Yes | String | The premium content you’re sending the subscriber. |
| keyword | Recommended | String | The activation keyword of the offer you are replying on. Send it whenever one shortcode carries more than one premium offer — it is what tells us which one this message belongs to. premium_id is accepted as an alternative. |
| linkId | Yes | String | The ID Sozuri delivered in the incoming-keyword webhook. Mandatory for billing this message as on-demand. |
| campaign | No | String | Optional label for grouping reports. |
| apiKey | — | String | Your project API key. Recommended: pass as a Bearer token instead. |
Response parameters
Synchronous JSON response confirming the premium message was accepted for delivery:
| Field | Type | Description |
|---|---|---|
| messageData.messages | Number | Total messages accepted. |
| recipients[].messageId | String | Unique Sozuri ID for this message. This is the same id the delivery webhook reports, so store it to correlate the two. |
| recipients[].to | String | The recipient’s phone number. |
| recipients[].status | String | Acceptance status — accepted, unknown_number etc. Not the final delivery status. |
| recipients[].statusCode | String | Numeric status — see status codes. |
| recipients[].messagePart | Number | SMS parts used. One GSM-7 part is 160 characters. |
| recipients[].keyword | String | The offer’s activation keyword. |
| recipients[].linkId | String | Echoes back the linkId this reply was billed against. |
| recipients[].type | String | Always ondemand here. |
Sample request
POST /api/v1/messaging/sendpremium HTTP/1.1
Host: sozuri.net
Authorization: Bearer LOx5JPdqf0lvf.......R9X9XDJ4PFxRqVrt9dx83cWiwfTQMF
Content-Type: application/json
Accept: application/json
{
"project": "my project",
"from": "23546",
"number": "2547251642xx",
"campaign": "Promo Nai",
"channel": "premium",
"message": "Test SMS.",
"type": "ondemand",
"linkId": "54785454",
"keyword": "Omoka"
}
POST /api/v1/messaging/sendpremium HTTP/1.1
Host: sozuri.net
Authorization: Bearer LOx5JPdqf0lvf45EZAQMJ.......SUzyxR9X9XDJ4PFxRqVrt9dx83cWiwfTQMF
Content-Type: application/json
Accept: application/xml
<request>
<project>my project</project>
<from>23546</from>
<number>2547251642xx</number>
<campaign>Promo Nai</campaign>
<channel>premium</channel>
<message>Test SMS.</message>
<type>ondemand</type>
<linkId>54785454</linkId>
<keyword>Omoka</keyword>
</request>
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sozuri.net/api/v1/messaging/sendpremium",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
"project" => "my project",
"from" => "23546",
"to" => "2547251642xx",
"campaign" => "Promo Nai",
"channel" => "premium",
"message" => "Test SMS.",
"type" => "ondemand",
"linkId" => "54785454",
"keyword" => "Omoka",
]),
CURLOPT_HTTPHEADER => [
"Accept: application/json",
"Authorization: Bearer LOx5JPdqf0lvf45EZAQMJm85OSUzyxR9X9XDJ4PFxRqVrt9dx83cWiwfTQMF",
"Content-Type: application/json",
],
]);
echo curl_exec($curl);
curl_close($curl);
const response = await fetch("https://sozuri.net/api/v1/messaging/sendpremium", {
method: "POST",
headers: {
"Authorization": "Bearer LOx5JPdqf0lvf45EZAQMJm85OSUzyxR9X9XDJ4PFxRqVrt9dx83cWiwfTQMF",
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
project: "my project",
from: "23546",
number: "2547251642xx",
campaign: "Promo Nai",
channel: "premium",
message: "Test SMS.",
type: "ondemand",
linkId: "54785454",
keyword: "Omoka"
})
});
console.log(await response.json());
require 'uri'
require 'net/http'
require 'json'
uri = URI("https://sozuri.net/api/v1/messaging/sendpremium")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer LOx5JPdqf0lvf45EZAQMJm85OSUzyxR9X9XDJ4PFxRqVrt9dx83cWiwfTQMF"
request["Content-Type"] = "application/json"
request["Accept"] = "application/json"
request.body = {
project: "my project", from: "23546", number: "2547251642xx",
campaign: "Promo Nai", channel: "premium", message: "Test SMS.",
type: "ondemand", linkId: "54785454", keyword: "Omoka"
}.to_json
puts http.request(request).body
import requests
response = requests.post(
"https://sozuri.net/api/v1/messaging/sendpremium",
headers={
"Authorization": "Bearer LOx5JPdqf0lvf45EZAQMJm85OSUzyxR9X9XDJ4PFxRqVrt9dx83cWiwfTQMF",
"Content-Type": "application/json",
"Accept": "application/json",
},
json={
"project": "my project", "from": "23546", "number": "2547251642xx",
"campaign": "Promo Nai", "channel": "premium", "message": "Test SMS.",
"type": "ondemand", "linkId": "54785454", "keyword": "Omoka",
},
)
print(response.json())
HttpResponse<String> response = Unirest.post("https://sozuri.net/api/v1/messaging/sendpremium")
.header("Authorization", "Bearer LOx5JPdqf0lvf45EZAQMJm85OSUzyxR9X9XDJ4PFxRqVrt9dx83cWiwfTQMF")
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.body("{\"project\":\"my project\",\"from\":\"23546\",\"to\":\"2547251642xx\","
+ "\"campaign\":\"Promo Nai\",\"channel\":\"premium\",\"message\":\"Test SMS.\","
+ "\"type\":\"ondemand\",\"linkId\":\"54785454\",\"keyword\":\"Omoka\"}")
.asString();
var client = new RestClient("https://sozuri.net/api/v1/messaging/sendpremium");
var request = new RestRequest(Method.POST);
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer LOx5JPdqf0lvf45EZAQMJm85OSUzyxR9X9XDJ4PFxRqVrt9dx83cWiwfTQMF");
request.AddParameter("application/json",
"{\"project\":\"my project\",\"from\":\"23546\",\"to\":\"2547251642xx\","
+ "\"campaign\":\"Promo Nai\",\"channel\":\"premium\",\"message\":\"Test SMS.\","
+ "\"type\":\"ondemand\",\"linkId\":\"54785454\",\"keyword\":\"Omoka\"}",
ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
curl -X POST https://sozuri.net/api/v1/messaging/sendpremium \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer LOx5JPdqf0lvf45EZAQMJm85OSUzyxR9X9XDJ4PFxRqVrt9dx83cWiwfTQMF' \
-d '{
"project": "my project",
"from": "23546",
"number": "2547251642xx",
"campaign": "Promo Nai",
"channel": "premium",
"message": "Test SMS.",
"type": "ondemand",
"linkId": "54785454",
"keyword": "Omoka"
}'
Sample JSON response
{
"messageData": { "messages": 1 },
"recipients": [
{
"messageId": "MSGBLK6012A7E8B90A21611835368",
"number": "2547251642xx",
"status": "accepted",
"statusCode": "11",
"keyword": "Omoka",
"messagePart": 1,
"type": "ondemand"
}
]
}
Incoming keyword webhook
When a subscriber texts your keyword to your premium shortcode, Sozuri pushes this webhook to the
callback URL on the premium service (set it under Premium Services;
a comma-separated list is allowed and each URL receives a copy).
Capture the linkId — your reply must quote it, and it is the only thing
that authorises the charge.
{
"project": "yourproject_name",
"shortcode": "20555",
"keyword": "QUIZ",
"number": "254748250794",
"messageId": "00073110187845156614979911",
"network": "safaricom",
"type": "linkNotification",
"linkId": "00073110187845156614979911",
"message": "QUIZ",
"status": "QUIZ",
"timestamp": 1774448390,
"authKey": "your_project_auth_key"
}
linkId is short-lived. Reply while the subscriber is still expecting the
content — a stale one is refused with carrier code 858
(LinkId Expired or Does Not Exist) and nothing is charged. Every request and its reply, and which
requests are still unanswered, are on the On-demand premium page in your dashboard.
Delivery status webhook
Sozuri sends a delivery callback when the carrier confirms (or rejects) your premium message:
{
"project": "yourproject_name",
"shortcode": "20555",
"keyword": "QUIZ",
"number": "254748250794",
"messageId": "SOZPREM68B6F1A2C3D4E",
"network": "safaricom",
"type": "premiumDelivery",
"status": "DeliveryToTerminal",
"timestamp": 1774448390,
"authKey": "your_project_auth_key"
}
messageId is the recipients[].messageId the send returned, so the two can be joined
directly. status is the carrier's own delivery description —
DeliveryToTerminal means delivered and charged; an absent-subscriber or delivery-impossible
description means it was not.
Error responses
On-demand premium SMS uses the SDP error envelope: { "status": "error", "description": "…" },
always with HTTP 200. Auth errors (Unknown project.) follow the SMS envelope and are documented on
the Authentication page.
| description | What it means |
|---|---|
Unknown or Inactive Premium service | No active premium offer matches the from shortcode / keyword you sent. |
linkId is required for an on-demand premium message | On-demand replies must quote the linkId from the incoming webhook. |
Unknown link id | We have no record of that linkId for this offer. Check you are sending the one from the most recent request. |
Unsupported Number | Not a Kenyan mobile number. |
The link id has expired… (carrier code 858) | The carrier refused the charge because the session had closed. Ask the subscriber to text the keyword again. |
The subscriber does not have enough airtime… (carrier code 826) | Nothing was charged and nothing was delivered. |
This shortcode is blacklisted by the carrier (carrier code 827) | Contact support — no message on this shortcode will be accepted. |
Where a carrier code is shown it is Safaricom's own statusCode, passed through so you can branch
on it.
| Condition | HTTP | Response body |
|---|---|---|
| Keyword/shortcode pair doesn’t match an active premium service on this project. | 400 | |
The linkId on a paid reply doesn’t match an incoming-keyword event we issued. |
400 | |
Offer reference not found (e.g. you sent an offer_code that’s not configured). |
400 | |
| Destination phone number is malformed or not in supported E.164 format. | 400 | |
Use cases
Where on-demand premium SMS earns its keep.
Sports scores & alerts
Subscriber texts GOAL to get the latest score on demand — charge per request, no app required.
News & horoscopes
Daily snippets of paid content on demand — the foundation of the original mobile content businesses.
Quizzes & trivia
Pay-per-question trivia campaigns on radio or TV — the keyword becomes the call-to-action.
A - Nigeria
B - Cote d’Ivoire
C - South Africa 08:42
Monetise your content via SMS.
Apply for a premium shortcode and we’ll handle billing, settlement and carrier paperwork end-to-end.