Apple Messages for Business
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page was written on 2026-10-01 against the Apple Messages for Business documentation published on Apple Business Register (the end-to-end guide and the MSP onboarding page), with the LangChain 1.4.3 core on FastAPI 0.142.2 (Python 3.12) and the Spring AI 2.0.1 core on Spring Boot 4.1.1 (Java 21); see the version baseline. The snippets are illustrative: they are written from the official documentation of FastAPI and Spring and are not compiled or run by this repository.
What was and was not verified. Unlike the self-hosted channels in this sub-section, this one could not be exercised with a sandbox: Apple Messages for Business needs an approved business account at Apple Business Register and a contract with a Messaging Service Provider, and the author has neither. The page was therefore documented from official Apple documentation only, read on 2026-10-01. Apple’s public pages describe the registration flow, the roles and the feature set; the message payload formats and the API between your platform and Apple are published for MSPs, so the webhook payload used below is a deliberately generic, hypothetical shape that you must replace with the one your MSP documents. No claim on this page was confirmed by sending a message through Apple.
What it is
Apple Messages for Business lets a company hold a conversation with a customer inside the Messages app that is already on every iPhone, iPad and Mac. The customer sees a verified business name, logo and conversation history, with the same look and feel as a chat with a friend, plus structured interactions (pickers, payments, sign-in) that plain text cannot express. For a documentation assistant this is a way to reach users of an Apple-centric product audience without asking them to install anything.
The important architectural fact is that the channel is business-initiated and business-hosted through a partner: you do not receive iMessages in your own server from Apple. Apple connects to a Messaging Service Provider, and your assistant sits behind that provider. This is the opposite of a Telegram bot or a Slack app, where you register a token and talk to the platform directly.
The MSP requirement
An MSP is a company that operates a messaging platform which is connected to Apple. Apple’s onboarding page for MSPs describes the expected product: a platform that supports asynchronous conversations and intent-based routing, a channel connector to Apple, an agent console for live agents, bot flows for triage, and a customer success team that builds the backend integrations (CRM, order management, authentication) and helps with conversation design. MSPs go through three phases with Apple: integrating and demonstrating the Apple features and passing a live review; registering a first set of client brands, building their workflows and piloting; and launching with Apple’s operations team after goal and experience reviews.
There are therefore three ways to get your assistant on this channel.
| Option | What you do | When it fits |
|---|---|---|
Use an existing MSP |
Register your brand at Apple Business Register, connect it to the MSP’s console, and plug the DocsAssistant core into the MSP’s bot or webhook mechanism. |
Almost always. This is the path Apple describes for businesses. |
Use a CCaaS or CRM platform that is an MSP |
Same as above, but the MSP is a contact-centre product you may already own (for example Dynamics 365 Contact Center, Zendesk, Salesforce or similar). The assistant becomes a bot participant in the platform’s routing. |
You already route human agents through that product and want one queue for bots and people. |
Become an MSP |
Build the full platform and pass Apple’s onboarding. |
Only if messaging is your product. It is out of proportion for a single assistant. |
Which vendors are currently approved MSPs is listed by Apple and changes over time; check the Apple Business Register site rather than relying on a list written here.
The Apple Business Register flow
Apple’s end-to-end guide describes a sequence that starts well before any code. In summary:
-
Create an organisation at Apple Business Register and verify the company.
-
Create a Messages for Business account: business name, logo, brand colours and the hours and messaging experience. Apple reviews it; the guide says approval takes days, so plan for it in the project schedule.
-
Add team members to the Apple Business Register account.
-
Connect the account to the MSP’s console. The MSP supplies the endpoint configuration; this is where your assistant’s webhook is registered on the MSP side.
-
Test with the MSP’s test tooling, then submit the experience for Apple’s review and launch. Apple’s guide then recommends continuous optimisation in review cycles after launch.
(account, branding, review)"] --> MSP["Messaging Service Provider
(console, routing, bot hooks)"] MSP --> HOOK["Your webhook
(channel adapter)"] HOOK --> CORE["DocsAssistant core
ask(conversationId, userId, text)"] CORE --> HOOK HOOK --> MSP MSP --> APPLE["Apple Messages
(customer's device)"] APPLE --> MSP
Because the registration is organisational, it is the lead time, not the integration, that dominates the schedule. The adapter itself is small; the account approval, the MSP contract and the content review are not.
Entry points
Conversations on this channel start from entry points that Apple approves and the customer chooses, not from an arbitrary phone number or handle. The end-to-end guide names two discovery mechanisms.
| Entry point | Meaning for the DocsAssistant |
|---|---|
Messages button |
A button on your website or in your app that opens a Messages conversation with your business. This is the natural place for "Ask the docs" next to search. |
Message Suggest |
Apple surfaces a "message" option for your business in places where Apple already shows it, such as search results or maps cards for a business listing. It depends on your business being a listed entity. |
The consequence for design is that a customer arrives with some context (the page they came from, if the MSP passes it on) and a welcome message is expected. The first turn of the conversation is therefore predictable: send a greeting and a quick-reply menu, then let free text go to the assistant.
Rich features
Apple’s guide lists structured interactions that go beyond text. Which of them your MSP exposes to bot developers varies, so treat the table as a menu of what the channel supports and ask your MSP what its bot API maps to.
| Feature | Use with a RAG assistant |
|---|---|
Quick replies |
A short menu under the message, for example "Installation", "Reference", "Talk to a person". Good for the greeting and for narrowing an ambiguous question. |
List picker |
A scrollable list of options the customer selects from. Use it to present the top retrieved documents as a choice ("Which of these did you mean?") or for a satisfaction survey at the end (the guide mentions CSAT list pickers). |
Apple Pay |
A payment request inside the conversation. Rarely relevant to a documentation assistant; relevant if the same assistant also sells support plans. Payment details are handled by Apple and the payment processor, never by your core. |
Authentication |
A sign-in flow that proves the customer’s identity to your backend, so the assistant can apply document-level permissions. This is how the channel’s identity problem is solved; see below. |
Two of these interact with the core’s design. A list picker returns an identifier, not text, so the adapter must
translate the selection back into something the core understands (usually a text such as "Open document 3" plus the
document id). And authentication gives you a verified user identity that the adapter can pass as userId, which lets
the core apply the filters described in Access Control & Privacy
instead of treating every Messages user as anonymous.
Mapping the channel onto the core
The core exposes ask(conversationId, userId, text) and streams tokens, citations and a final done event. Apple
Messages has no token streaming: a message is delivered whole. The adapter therefore consumes the entire stream, builds
one answer, and sends it. The mapping is:
| Channel concept | Core concept |
|---|---|
Conversation / thread identifier supplied by the MSP |
|
Customer identifier (pseudonymous per business, as exposed by the MSP) or the authenticated identity |
|
Customer text |
|
|
Accumulated into one string; no partial sending |
|
Rendered as a short "Sources" list in the same message, or as a list picker of documents |
Core error or timeout |
A graceful fallback message and, where offered, a handoff to a person |
Integrating the core behind an MSP
The generic MSP integration pattern has four moving parts, and it is the same whichever MSP you choose.
-
Inbound webhook. The MSP calls an HTTPS endpoint you own for each customer message. The call must be authenticated; most platforms sign the body with a shared secret or send a bearer token.
-
Fast acknowledgement. Respond with a 2xx quickly. Generation takes seconds, and platforms retry slow or failed webhooks, so the answer is produced after the acknowledgement.
-
Outbound reply. Your adapter calls the MSP’s send API with the conversation identifier and the answer. That API, and how it models rich messages, is the MSP’s, not Apple’s, to document.
-
Idempotency. Retries and duplicate deliveries happen, so the adapter remembers message identifiers it has handled.
Source: Apple — MSP onboarding
Python adapter over the LangChain core
The sketch below uses FastAPI. The request body is a hypothetical, simplified MSP payload: the field names
(messageId, conversationId, customerId, text) are placeholders for whatever your MSP defines. The signature check
uses an HMAC over the raw body, a common pattern; your MSP’s scheme may differ. The secret and the MSP API token are read
from environment variables.
import hashlib
import hmac
import logging
import os
import httpx
from fastapi import BackgroundTasks, FastAPI, Header, HTTPException, Request
from docs_assistant.core import ask # the channel-agnostic core (async generator of events)
app = FastAPI()
log = logging.getLogger("apple_webhook")
WEBHOOK_SECRET = os.environ["MSP_WEBHOOK_SECRET"] # placeholder: set in your environment
MSP_API_URL = os.environ["MSP_API_URL"] # placeholder: your MSP's send endpoint
MSP_API_TOKEN = os.environ["MSP_API_TOKEN"] # placeholder: your MSP's credential
seen: set[str] = set() # use Redis with a TTL in production
def verify(body: bytes, signature: str) -> bool:
expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
async def answer_and_reply(msp_conversation_id: str, customer_id: str, text: str) -> None:
parts: list[str] = []
sources: list[str] = []
# Prefixed keys are only for the assistant's memory; the MSP gets its own raw id back.
async for event in ask(f"apple:{msp_conversation_id}", f"apple:{customer_id}", text):
if event["type"] == "token":
parts.append(event["text"])
elif event["type"] == "citation":
sources.append(event["title"])
reply = "".join(parts) or "Sorry, I could not find an answer."
if sources:
reply += "\n\nSources: " + ", ".join(sources[:3])
async with httpx.AsyncClient(timeout=10) as client:
response = await client.post(
MSP_API_URL,
headers={"Authorization": f"Bearer {MSP_API_TOKEN}"},
json={"conversationId": msp_conversation_id, "text": reply},
)
try:
response.raise_for_status()
except httpx.HTTPStatusError:
log.exception("MSP rejected reply for conversation %s", msp_conversation_id)
raise
@app.post("/apple/webhook", status_code=202)
async def webhook(request: Request, background: BackgroundTasks,
x_signature: str = Header(...)):
body = await request.body()
if not verify(body, x_signature):
raise HTTPException(status_code=401)
msg = await request.json()
if msg["messageId"] in seen:
return {"status": "duplicate"}
seen.add(msg["messageId"])
background.add_task(
answer_and_reply,
msg["conversationId"],
msg["customerId"],
msg["text"],
)
return {"status": "accepted"}
Source: FastAPI — Background Tasks. The HMAC pattern follows Python — hmac.
Background tasks run in the same process after the response is sent, which is enough for a sketch. For production use a durable queue so that a restart does not lose answers; the deployment options are in LangChain: Deployment.
Java adapter over the Spring AI core
The Spring version has the same shape. It receives the signed webhook, returns 202, and answers on a separate executor.
DocsAssistantCore is the application’s own wrapper around the ChatClient with the RAG advisor and JDBC chat memory;
it is assumed to expose a blocking answer(conversationId, userId, text) that collects the stream.
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestClient;
@RestController
class AppleMessagesWebhook {
private static final Logger log = LoggerFactory.getLogger(AppleMessagesWebhook.class);
record Inbound(String messageId, String conversationId, String customerId, String text) {}
private final DocsAssistantCore core;
private final RestClient msp;
private final byte[] secret;
private final com.fasterxml.jackson.databind.ObjectMapper mapper;
private final Set<String> seen = ConcurrentHashMap.newKeySet(); // Redis with TTL in production
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
AppleMessagesWebhook(DocsAssistantCore core,
com.fasterxml.jackson.databind.ObjectMapper mapper,
RestClient.Builder builder,
@Value("${MSP_API_URL}") String mspUrl, // placeholder
@Value("${MSP_API_TOKEN}") String mspToken, // placeholder
@Value("${MSP_WEBHOOK_SECRET}") String secret) { // placeholder
this.core = core;
this.mapper = mapper;
this.msp = builder.baseUrl(mspUrl)
.defaultHeader("Authorization", "Bearer " + mspToken).build();
this.secret = secret.getBytes(StandardCharsets.UTF_8);
}
@PostMapping("/apple/webhook")
ResponseEntity<Map<String, String>> receive(@RequestBody byte[] body,
@RequestHeader("X-Signature") String signature)
throws Exception {
if (!valid(body, signature)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
Inbound in = mapper.readValue(body, Inbound.class);
if (!seen.add(in.messageId())) {
return ResponseEntity.accepted().body(Map.of("status", "duplicate"));
}
CompletableFuture.runAsync(() -> {
// Prefixed keys are only for the assistant's memory; the MSP gets its own raw id back.
String reply = core.answer("apple:" + in.conversationId(), "apple:" + in.customerId(), in.text());
msp.post().body(Map.of("conversationId", in.conversationId(), "text", reply))
.retrieve().toBodilessEntity();
}, executor).exceptionally(e -> {
log.error("Failed to answer conversation {}", in.conversationId(), e);
return null;
});
return ResponseEntity.accepted().body(Map.of("status", "accepted"));
}
private boolean valid(byte[] body, String signature) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret, "HmacSHA256"));
String expected = java.util.HexFormat.of().formatHex(mac.doFinal(body));
return MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
}
}
Source: Spring Framework — REST Clients and Spring Boot — Task Execution. The assistant beans themselves are described in Spring AI: ChatModel & ChatClient, Advisors and Chat Memory.
The Spring controller above is a sketch to show the pattern; keep the secret out of source control and application properties checked into the repository, for example by reading it from a secret store (Azure Key Vault & Secrets covers one).
Behind a CCaaS
Many organisations meet this channel not through a bare MSP but through a contact-centre platform (CCaaS) that is an MSP, such as Dynamics 365 Contact Center or Zendesk. In that setup your adapter is not the first thing the customer’s message touches: the platform owns the conversation, its queue and its human agents, and the assistant is one more participant.
-
The platform calls the assistant. Most CCaaS products support a bot or "virtual agent" connector that posts the customer message to an HTTPS endpoint and expects a reply. That endpoint is the adapter above.
-
Escalation is a routing event. When the core cannot answer, or the customer asks for a person, the adapter returns a handoff signal in the form the platform defines, and the platform moves the conversation to a human queue with the transcript. The decision logic is the same one described in Human in the Loop.
-
History lives in two places. The platform keeps the transcript; the core keeps its own memory in the checkpointer or chat-memory repository (see LangChain: Short-Term Memory). Use the platform’s conversation id as
conversationIdso the two stay aligned. -
Compliance moves to the platform. Retention, consent and agent access to transcripts are governed by the platform’s settings in addition to yours.
Limitations
-
No public iMessage bot API. There is no endpoint with which an arbitrary developer can create an iMessage account or bot and message people. The only sanctioned route for a business is Apple Messages for Business through Apple Business Register and an MSP. Unofficial iMessage automation exists, but it depends on private protocols and personal accounts and is not a basis for a product.
-
Customer-initiated. Conversations start from an entry point the customer chooses. You cannot message a list of phone numbers cold.
-
No token streaming. Each reply is a whole message. Keep answers short, and send a quick acknowledgement for long retrievals if your MSP supports a typing indication.
-
Capabilities depend on the MSP. The rich features Apple supports are only usable to the extent the MSP exposes them to bot developers. Test the actual feature set before designing around a list picker or authentication.
-
Lead time and review. Account approval, MSP onboarding and Apple’s experience review precede any real traffic.
-
Apple-only audience. Only Apple devices can use it. For a cross-platform business-messaging reach, compare WhatsApp Business and RCS Business Messaging.
-
Documentation scope. This page rests on public Apple documentation only. Payload formats, API limits and pricing are MSP-specific and are not covered.
Where to go next
The architecture that the adapter plugs into is in Channel-Agnostic Architecture, and the shared concerns of webhooks, retries, rate limits and monitoring are in Operating Chat Channels. The core’s streaming behaviour is described in LangChain: Streaming and Spring AI: Streaming & Reactive; this channel is a case where you deliberately ignore it. Source citations are covered in Citations & Grounding.