Receive the replies people send to your numbers.
Choose webhook to have each reply posted to your server within seconds, or collect to fetch new replies with a GET request when it suits you. Replies to an alphanumeric sender such as everymessage are not possible over SMS, so send from a number if you want answers.
Webhook
We POST new messages to your URL as JSON. Tell us the URL and we will connect it to your numbers.
https://your-server.example.com/…{
"list": [
{
"type": "INBOUND_MESSAGE",
"messageId": 90321,
"sender": "+447700900123",
"inboxName": "support",
"inboxNumber": "447700900000",
"content": "Yes, 10:30 is fine",
"dateTime": "2026-10-11T09:31:12.408Z"
}
]
}
| Field | Type | Description |
|---|---|---|
list | array | One or more messages. Usually one, but handle several. |
type | string | INBOUND_MESSAGE. |
messageId | integer | Our id for the message. Use it to ignore a repeat delivery. |
sender | string | The mobile number that sent it. |
inboxName | string | The inbox the number belongs to, if any. |
inboxNumber | string | Your number that received it. |
inboxKeyword | string | The keyword matched, if your number uses keywords. Left out otherwise. |
content | string | The message text. |
dateTime | string | When it was received, ISO 8601 in UTC. |
Your response
Reply with any 2xx status within 10 seconds. If we get an error or no answer, we retry the batch several times over the next few minutes, so make your handler safe to run twice for the same messageId. Older integrations also return {"echo":"ACK"}; that is accepted but not required.
Securing your webhook
- Use HTTPS.
- Put a long random token in the URL, for example
https://example.com/everymessage/inbound/3f9c…, and reject any request without it. The code examples below do this. - Optionally allow only
13.134.161.74, the fixed address our webhooks come from.
Collect by API
https://api.everymessage.com/v1/inbound_messagesReturns messages you have not collected yet, and marks them collected. Each message is returned once, so store it before your next call. When there is nothing new, responseStatusReason is NO_CURRENT_ENTRIES. Messages wait until you collect them; they are not deleted if you poll less often.
| Parameter | Type | Description |
|---|---|---|
inbox | string | Limit to one or more inboxes, by id or name. Repeat it, or separate with commas. Omit, or use *, for all. |
limit | integer | Maximum messages per call. Default 500, maximum 1000. Call again until you get NO_CURRENT_ENTRIES to clear a backlog. |
Parameters can also be sent as a JSON body with POST /v1/inbound_messages.
{
"apiName": "everymessage Web API",
"apiVersion": "1.0.2",
"apiTime": "11 October 2026 10:32:00",
"apiState": "RUNNING",
"responseStatusCode": 200,
"processingTime": 12,
"body": [
{
"type": "INBOUND_MESSAGE",
"messageId": 90321,
"sender": "447700900123",
"inboxName": "support",
"inboxNumber": "447700900000",
"content": "Yes, 10:30 is fine",
"dateTime": "2026-10-11 10:31:12"
}
]
}
The fields match the webhook, except that dateTime is UK local time in the form yyyy-MM-dd HH:mm:ss and sender has no +. Use GET /v1/get_inboxes to list your inboxes.
Code examples
Webhook receiver
Set EmWebhookToken (C#) or EM_WEBHOOK_TOKEN (Python) to a long random value and give us the URL including it. The same app also receives delivery receipts. The C# example is an ASP.NET Core minimal API (dotnet new web); the Python example needs pip install fastapi uvicorn.
C#
// Receive inbound SMS and delivery receipts by webhook (ASP.NET Core minimal API, .NET 8 or later)
using System.Security.Cryptography;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// A long random token in the URL stops anyone else posting to your endpoints.
var token = Encoding.UTF8.GetBytes(app.Configuration["EmWebhookToken"]
?? throw new InvalidOperationException("Set EmWebhookToken"));
bool Valid(string key) => CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(key), token);
app.MapPost("/everymessage/inbound/{key}", (string key, CallbackBatch<InboundMessage> batch,
ILogger<Program> log) =>
{
if (!Valid(key)) return Results.NotFound();
foreach (var m in batch.List)
log.LogInformation("SMS {Id} from {Sender} to {Inbox}: {Content}",
m.MessageId, m.Sender, m.InboxNumber, m.Content);
// Any 2xx response tells everymessage the batch was received.
return Results.Ok(new { echo = "ACK" });
});
app.MapPost("/everymessage/receipts/{key}", (string key, CallbackBatch<DeliveryReceipt> batch,
ILogger<Program> log) =>
{
if (!Valid(key)) return Results.NotFound();
foreach (var r in batch.List)
log.LogInformation("Message {Id} ({Reference}) to {Recipient}: {Status}",
r.MessageId, r.Reference, r.Recipient, r.Status);
return Results.Ok(new { echo = "ACK" });
});
app.Run();
record CallbackBatch<T>(IReadOnlyList<T> List);
record InboundMessage(string Type, long MessageId, string Sender, string? InboxName,
string? InboxNumber, string? InboxKeyword, string Content, DateTimeOffset DateTime);
record DeliveryReceipt(string Type, long MessageId, string? Reference, string Recipient,
string Status, DateTimeOffset DateTime);
Python
"""Receive inbound SMS and delivery receipts by webhook (pip install fastapi uvicorn).
Run with: uvicorn webhooks:app --host 0.0.0.0 --port 8000
"""
import os
import secrets
from datetime import datetime
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI()
# A long random token in the URL stops anyone else posting to your endpoints.
TOKEN = os.environ["EM_WEBHOOK_TOKEN"]
class InboundMessage(BaseModel):
type: str
messageId: int
sender: str
inboxName: str | None = None
inboxNumber: str | None = None
inboxKeyword: str | None = None
content: str
dateTime: datetime
class DeliveryReceipt(BaseModel):
type: str
messageId: int
reference: str | None = None
recipient: str
status: str
dateTime: datetime
class InboundBatch(BaseModel):
items: list[InboundMessage] = Field(alias="list")
class ReceiptBatch(BaseModel):
items: list[DeliveryReceipt] = Field(alias="list")
def check(key: str) -> None:
if not secrets.compare_digest(key, TOKEN):
raise HTTPException(status_code=404)
@app.post("/everymessage/inbound/{key}")
def inbound(key: str, batch: InboundBatch):
check(key)
for m in batch.items:
print(f"SMS {m.messageId} from {m.sender} to {m.inboxNumber}: {m.content}")
# Any 2xx response tells everymessage the batch was received.
return {"echo": "ACK"}
@app.post("/everymessage/receipts/{key}")
def receipts(key: str, batch: ReceiptBatch):
check(key)
for r in batch.items:
print(f"Message {r.messageId} ({r.reference}) to {r.recipient}: {r.status}")
return {"echo": "ACK"}
Collect by API
Polls once a minute and prints each new message.
C#
// Collect inbound SMS replies with the everymessage v1 API (.NET 8 or later)
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
var apiKey = Environment.GetEnvironmentVariable("EM_API_KEY")!;
var secretKey = Environment.GetEnvironmentVariable("EM_SECRET_KEY")!;
using var http = new HttpClient { BaseAddress = new Uri("https://api.everymessage.com/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:{secretKey}")));
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
do
{
// Each message is returned once: store it before the next call.
var result = await http.GetFromJsonAsync<ApiEnvelope<List<InboundMessage>>>(
"v1/inbound_messages?limit=500")
?? throw new InvalidOperationException("Empty response from everymessage");
switch (result.ResponseStatusReason)
{
case null:
foreach (var m in result.Body ?? [])
Console.WriteLine($"{m.DateTime} from {m.Sender} to {m.InboxNumber}: {m.Content}");
break;
case "NO_CURRENT_ENTRIES":
break; // nothing new
default:
throw new InvalidOperationException($"Collect failed: {result.ResponseStatusReason}");
}
}
while (await timer.WaitForNextTickAsync());
record ApiEnvelope<T>(int ResponseStatusCode, string? ResponseStatusReason, int ProcessingTime, T? Body);
record InboundMessage(string Type, long MessageId, string Sender, string? InboxName,
string? InboxNumber, string? InboxKeyword, string Content, string DateTime);
Python
"""Collect inbound SMS replies with the everymessage v1 API (Python 3.10+, pip install httpx)."""
import os
import time
import httpx
with httpx.Client(
base_url="https://api.everymessage.com",
auth=(os.environ["EM_API_KEY"], os.environ["EM_SECRET_KEY"]),
timeout=30,
) as client:
while True:
# Each message is returned once: store it before the next call.
response = client.get("/v1/inbound_messages", params={"limit": 500})
response.raise_for_status()
result = response.json()
reason = result.get("responseStatusReason")
if reason is None:
for m in result["body"]:
print(f"{m['dateTime']} from {m['sender']} to {m['inboxNumber']}: {m['content']}")
elif reason != "NO_CURRENT_ENTRIES":
raise RuntimeError(f"Collect failed: {reason}")
time.sleep(60)
