We're excited to announce a significant improvement to our webhook payload structure. Based on user feedback and real-world usage, we've added new fields to make webhook data more explicit, especially for multi-SIM devices and complex messaging scenarios.
TL;DR: Your webhooks now include sender and recipient fields for crystal-clear phone number semantics, while maintaining full backward compatibility with the existing phoneNumber field.
{
"payload": {
"messageId": "abc123",
"phoneNumber": "+1234567890",
"simNumber": 1,
"message": "Hello World"
}
}
The Confusion: What does phoneNumber represent?
- For incoming SMS: Is it the sender or the receiver?
- For outgoing SMS: Is it the recipient or the sender?
- With multiple SIMs: Which device number is being referenced?
This ambiguity forced developers to: - Reverse-engineer semantics based on event type - Maintain separate logic for inbound vs outbound - Guess which phone number they're actually working with
Consider a dual-SIM device with numbers +1112223333 (SIM 1) and +4445556666 (SIM 2):
Scenario 1: Incoming SMS to SIM 1
{
"phoneNumber": "+9998887777", // Who sent it? Which SIM received it?
"simNumber": 1
}
Scenario 2: Outgoing SMS from SIM 2
{
"phoneNumber": "+9998887777", // Who sent it? Who received it?
"simNumber": 2
}
In both cases, phoneNumber means something different! 😕
{
"payload": {
"messageId": "abc123",
"sender": "+9998887777",
"recipient": "+1112223333",
"phoneNumber": "+9998887777",
"simNumber": 1,
"message": "Hello World"
}
}
Now it's crystal clear: - sender: The phone number that sent the message - recipient: The phone number that received the message (or null if unavailable) - phoneNumber: Deprecated but kept for backward compatibility
| Event Type | sender | recipient |
sms:received (inbound) | External sender | Device's number |
sms:data-received (inbound) | External sender | Device's number |
mms:received (inbound) | External sender | Device's number |
sms:sent (outbound) | Device's number | Recipient |
sms:delivered (outbound) | Device's number | Recipient |
sms:failed (outbound) | Device's number | Recipient |
Key Insight: sender always originates, recipient always receives. No more guessing! 🎯
We understand that changing payload structures can be disruptive. That's why we've designed this enhancement to be 100% backward compatible.
- ✅ Existing
phoneNumber field remains in all payloads - ✅ Same data values (no semantic changes to
phoneNumber) - ✅ All existing webhook consumers continue to work without modification
- ✅ No changes to webhook registration or delivery mechanisms
- ⭐
sender field: Originating phone number (who sent the message) - 📍
recipient field: Receiving phone number — device's number for inbound, recipient's for outbound - ⚠️
phoneNumber field: Now marked as deprecated
Phase 1 - Today (Backward Compatible):
{
"sender": "+1234567890",
"recipient": "+9876543210",
"phoneNumber": "+1234567890" // Still works!
}
Phase 2 - Future (After Deprecation Period):
{
"sender": "+1234567890",
"recipient": "+9876543210"
// phoneNumber removed in future major version
}
Recommendation: Start using sender and recipient in your code now. They're clearer and will be supported indefinitely. The phoneNumber field will remain available for at least 12 months before removal.
The new recipient field is especially valuable for multi-SIM devices.
Device Configuration: - SIM 1: +1112223333 - SIM 2: +4445556666
Incoming SMS to SIM 1:
{
"event": "sms:received",
"payload": {
"sender": "+9998887777",
"recipient": "+1112223333", // ← Now explicit!
"simNumber": 1
}
}
Incoming SMS to SIM 2:
{
"event": "sms:received",
"payload": {
"sender": "+7776665555",
"recipient": "+4445556666", // ← Clear which SIM!
"simNumber": 2
}
}
Now you can: - Route messages to correct handlers based on recipient number - Track which SIM received each message without inference - Build accurate multi-SIM analytics and dashboards
The recipient field is nullable because:
READ_PHONE_STATE permission may not be granted - Some carriers don't expose phone numbers programmatically
When recipient is null, webhooks still fire normally with the field set to null. This ensures no loss of functionality.
Always Handle null Values
Your webhook handler should always check if recipient is null before using it. Example in JavaScript:
const { sender, recipient } = req.body.payload;
console.log(`From: ${sender}, To: ${recipient || 'unknown'}`);
Use the new fields immediately—they're clearer and future-proof:
app.post('/webhook', (req, res) => {
const { sender, recipient, message } = req.body.payload;
console.log(`Message from ${sender} to ${recipient || 'unknown'}`);
// Your business logic here
res.sendStatus(200);
});
No action required! Your existing code will continue to work:
// Old code still works
app.post('/webhook', (req, res) => {
const { phoneNumber, message } = req.body.payload;
console.log(`Message from ${phoneNumber}`);
res.sendStatus(200);
});
But consider upgrading for better clarity:
// Enhanced code with new fields
app.post('/webhook', (req, res) => {
const { sender, recipient, message } = req.body.payload;
// Use sender (explicit) instead of phoneNumber (ambiguous)
console.log(`Message from ${sender} to ${recipient || 'unknown'}`);
res.sendStatus(200);
});
All documentation has been updated to reflect the new payload structure:
- Register a webhook for
sms:received event - Send an SMS to your device
- Inspect the webhook payload—you should see
sender and recipient fields
{
"deviceId": "device-id",
"event": "sms:received",
"id": "webhook-event-id",
"payload": {
"messageId": "abc123",
"sender": "+9998887777",
"recipient": "+1112223333",
"phoneNumber": "+9998887777",
"simNumber": 1,
"receivedAt": "2026-02-17T12:00:00.000+07:00"
},
"webhookId": "webhook-id"
}
A: No! The phoneNumber field remains with the same value as before. Your existing code will work without any changes.
A: We recommend it! The sender and recipient fields are much clearer, especially for multi-SIM scenarios. The migration depends on event type: - For inbound events (sms:received, sms:data-received, mms:received): replace phoneNumber with sender. - For outbound events (sms:sent, sms:delivered, sms:failed): replace phoneNumber with recipient.
A: We'll keep phoneNumber for at least 12 months to give everyone time to migrate. We'll announce a deprecation timeline well in advance.
A: This can happen if the app lacks READ_PHONE_STATE permission or the carrier doesn't provide the device number. Your code should handle null gracefully. The webhook will still fire with all other fields populated.
A: No. This enhancement only affects webhook payloads. The message sending API remains unchanged.
A: - Use sender for the originating phone number (who sent the message) - Use recipient for the receiving phone number (which device received it or who was the recipient) - Avoid phoneNumber in new code (it's deprecated)
| ✅ | Benefit | Impact |
| 1 | Clear Semantics | No more guessing what phoneNumber means |
| 2 | Multi-SIM Clarity | Explicit recipient per SIM slot |
| 3 | Zero Breaking Changes | Existing integrations keep working |
| 4 | Future-Proof | New fields will be supported indefinitely |
| 5 | Better Developer Experience | Self-documenting code with explicit field names |
| 6 | Consistent Logic | Same semantics across all event types |
We'd love to hear your thoughts on this enhancement!
This enhancement was driven by user feedback from the community. Your real-world experiences with multi-SIM devices and complex integrations helped shape this solution.
We're committed to making SMSGate the most developer-friendly SMS gateway platform. Stay tuned for more improvements!
Happy coding! 🚀