Incoming MO Messages
To receive incoming SMS, you must acquire one or more numbers from the number inventory and assign them to an API Key. Each API Key can have multiple numbers associated with it for receiving inbound messages.
Upon a receipt of an incoming SMS that has been sent to one of the numbers assigned to your account, then an Incoming Message will be posted to a Webhook address. This Webhook address can be set up in the Customer Portal as part of the configuration of the Number that you have selected from the number pool and assigned to your account.
The incoming message Webhook should be acknowledged with an HTTP 200 OK response. If the incoming message is unable to connect to the Webhook address or if a non HTTP 200 Ok response is received, it will be queued and re-tried for up to 24 hours before being discarded.
The Webhook call will be sent as a POST with a JSON document in the request body. It will be of the following format:
Endpoint
Webhook calls are sent as HTTP POST requests with a JSON body.
POST {Your Webhook URL registered in the Rakuten CPaaS or provided in request as "custom_callback_url"}
Request Parameters
List of JSON fields included in the Webhook POST body.
| Parameter | Required | Description/Values |
|---|---|---|
| message_id | Y | The message ID returned in the submit SMS API request |
| sender_address | Y | Sender phone number |
| destination_address | Y | Destination phone number assigned to your account |
| content_type | Y | Encoding type: text (GSM-7), unicode (UTF-16) |
| udh | Optional | Hex representation of the UDH (User Data Header) field, present in concatenated or specially encoded messages. Empty string if not used. |
| message_body | Y | The text content of the received SMS message |
Example Payload
Example JSON body received at your Webhook endpoint.
{
"message_id": "...the message id returned in the submit sms API request...",
"sender_address": "...sender address...",
"destination_address": "...destination phone number...",
"content_type": "text, unicode or binary",
"udh": "...hex representation of the udh field if present in the message...",
"message_body": "The contents of the message being delivered"
}
Sample Payload
{
"message_id": "msg-00123abc456def789",
"sender_address": "819012345678",
"destination_address": "815011112222",
"content_type": "unicode",
"udh": "",
"message_body": "Thank you for contacting us. Your inquiry number is 12345."
}
- cURL
- JavaScript
- Python
- PHP
- Java
Command to verify your Webhook endpoint in a local development environment.
curl -X POST http://localhost:3000/mo-webhook \
-H "Content-Type: application/json" \
-d '{
"message_id": "test-msg-001",
"sender_address": "819012345678",
"destination_address": "815011112222",
"content_type": "text",
"udh": "",
"message_body": "This is a test message"
}'
const express = require('express');
const app = express();
app.use(express.json());
app.post('/mo-webhook', (req, res) => {
const body = req.body;
if (!body || typeof body !== 'object' || Array.isArray(body)) {
return res.status(400).json({ error: 'Invalid or missing JSON body' });
}
const {
message_id = '',
sender_address = '',
destination_address = '',
content_type = '',
udh = '',
message_body = ''
} = body;
console.log(`[MO received] from=${sender_address} to=${destination_address}`);
console.log(` message_id : ${message_id}`);
console.log(` content_type : ${content_type}`);
console.log(` udh : ${udh || '(none)'}`);
console.log(` message_body : ${message_body}`);
return res.status(200).json({ status: 'received' });
});
app.use((err, req, res, next) => {
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
return res.status(400).json({ error: 'Invalid JSON body' });
}
return res.status(500).json({ error: 'Internal server error' });
});
app.listen(3000, () => console.log('Webhook server listening on port 3000'));;
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/mo-webhook', methods=['POST'])
def mo_webhook():
data = request.get_json(silent=True)
# Guard: reject requests with no JSON body
if data is None:
return jsonify({'error': 'Invalid or missing JSON body'}), 400
message_id = data.get('message_id', '')
sender_address = data.get('sender_address', '')
destination_address = data.get('destination_address', '')
content_type = data.get('content_type', '')
udh = data.get('udh', '')
message_body = data.get('message_body', '')
print(f'[MO received] from={sender_address} to={destination_address}')
print(f' message_id : {message_id}')
print(f' content_type : {content_type}')
print(f' udh : {udh or "(none)"}')
print(f' message_body : {message_body}')
# Add your business logic here
# Rakuten CPaaS expects HTTP 200 OK
return jsonify({'status': 'received'}), 200
if __name__ == '__main__':
# Development server only — use gunicorn for production:
# gunicorn -w 4 mo_webhook:app
app.run(port=3000)
<?php
$json = file_get_contents('php://input');
$data = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode(['error' => 'Invalid or missing JSON body']);
exit;
}
$message_id = $data['message_id'] ?? '';
$sender_address = $data['sender_address'] ?? '';
$destination_address = $data['destination_address'] ?? '';
$content_type = $data['content_type'] ?? '';
$udh = $data['udh'] ?? '';
$message_body = $data['message_body'] ?? '';
error_log("[MO received] from={$sender_address} to={$destination_address}");
header('Content-Type: application/json');
http_response_code(200);
echo json_encode(['status' => 'received']);
// Required dependencies (Maven):
// spring-boot-starter-web
// jackson-databind (included transitively with spring-boot-starter-web)
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
public class MoWebhookController {
@PostMapping("/mo-webhook")
public ResponseEntity<Map<String, String>> handleMo(
@RequestBody Map<String, String> payload) {
String messageId = payload.getOrDefault("message_id", "");
String senderAddress = payload.getOrDefault("sender_address", "");
String destinationAddress = payload.getOrDefault("destination_address", "");
String contentType = payload.getOrDefault("content_type", "");
String udh = payload.getOrDefault("udh", "");
String messageBody = payload.getOrDefault("message_body", "");
System.out.printf("[MO received] from=%s to=%s%n", senderAddress, destinationAddress);
System.out.printf(" message_id : %s%n", messageId);
System.out.printf(" content_type : %s%n", contentType);
System.out.printf(" udh : %s%n", udh.isEmpty() ? "(none)" : udh);
System.out.printf(" message_body : %s%n", messageBody);
// Add your business logic here
// Rakuten CPaaS expects HTTP 200 OK
return ResponseEntity.ok(Map.of("status", "received"));
}
}
Implementation Notes
| Item | Details |
|---|---|
| Response Requirement | Must return HTTP 200 OK. Any other response code triggers a retry. |
| Retry Policy | On connection failure or non-200 response, retried for up to 24 hours then automatically discarded. |
| Idempotency | Retries may deliver the same message_id more than once. Strongly recommended to implement deduplication using message_id. |
| content_type | When content_type is unicode, the body is UTF-16 encoded. SMS containing Japanese characters or emoji is typically unicode. |
| Concatenated SMS | Long messages are split and delivered in parts. The udh field contains concatenation reference, total parts, and sequence number — implement reassembly logic as needed. |
| HTTPS Required | Always use HTTPS for your Webhook URL in production environments. |