The XMTP backend sends push notifications through APNs, FCM, or an HTTPS webhook.
The Android, iOS, and Node SDKs register a recipient for the local database and keep subscriptions
current. The Browser SDK and WASM binding do not expose this API.
Ask the backend operator which channels are configured. See
Push configuration for server setup.
The Android example has an optional FCM build. The iOS example at apps/example-ios
has an optional Push configuration with APNs registration and a silent-push receiver.
Debug and Release keep push off. APNs delivery needs a signed device build and a backend
with the APNs channel configured; the local development backend does not configure APNs.
Enable, disable, override updates, and conversation state reads are asynchronous.
notificationState() is synchronous and reads local state. Failures throw an error
or reject a promise. Conversation methods are available on Group and Dm.
Method
Result
client.enableNotifications(config)
Store the config, register the recipient, and return its local state.
client.disableNotifications()
Disable locally, then unregister from the backend.
client.notificationState()
Read local state without a backend request.
conversation.setNotifications(value)
Set an override, or reset it to the config rules.
conversation.state()
Read common.notificationsEnabled for a group or notificationsEnabled for a DM.
The console.error() static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.
These examples show each operation. In an app, call disableNotifications only
when the user turns notifications off. Call enableNotifications again when the
provider token, webhook URL, or rules change. A running client resumes stored
notification work when it opens the same local database.
NotificationConfig.channel is required. The variants are Apns, Fcm, and
Http on Kotlin; .apns, .fcm, and .http on Swift; and objects with kind
equal to "apns", "fcm", or "http" on Node.
APNs and FCM need a token. HTTP needs an HTTPS url and a signingKey of 16 to 64 bytes.
Use a random 32-byte key. The receiver must keep the same signing key to verify requests.
Bytes use ByteArray, Data, and Uint8Array, respectively.
Generates cryptographically strong pseudorandom data. The size argument
is a number indicating the number of bytes to generate.
If a callback function is provided, the bytes are generated asynchronously
and the callback function is invoked with two arguments: err and buf.
If an error occurs, err will be an Error object; otherwise it is null. The buf argument is a Buffer containing the generated bytes.
// Asynchronous
const {
randomBytes,
} =awaitimport('node:crypto');
randomBytes(256, (err, buf) => {
if (err) throw err;
console.log(`${buf.length} bytes of random data: ${buf.toString('hex')}`);
});
If the callback function is not provided, the random bytes are generated
synchronously and returned as a Buffer. An error will be thrown if
there is a problem generating the bytes.
// Synchronous
const {
randomBytes,
} =awaitimport('node:crypto');
constbuf=randomBytes(256);
console.log(
`${buf.length} bytes of random data: ${buf.toString('hex')}`);
The crypto.randomBytes() method will not complete until there is
sufficient entropy available.
This should normally never take longer than a few milliseconds. The only time
when generating the random bytes may conceivably block for a longer period of
time is right after boot, when the whole system is still low on entropy.
This API uses libuv's threadpool, which can have surprising and
negative performance implications for some applications; see the UV_THREADPOOL_SIZE documentation for more information.
The asynchronous version of crypto.randomBytes() is carried out in a single
threadpool request. To minimize threadpool task length variation, partition
large randomBytes requests when doing so as part of fulfilling a client
request.
@since ― v0.5.8
@param ― size The number of bytes to generate. The size must not be larger than 2**31 - 1.
@return ― if the callback function is not provided.
randomBytes(32);
const
constchannel:NotificationChannel
channel:
typeNotificationChannel= {
readonlykind:"apns";
readonlytoken:string;
} | {
readonlykind:"fcm";
readonlytoken:string;
} | {
readonlykind:"http";
readonlyurl:string;
readonlysigningKey:Uint8Array;
}
NotificationChannel= {
kind: "http"
kind: "http",
url: string
url,
signingKey: Uint8Array<ArrayBufferLike>
signingKey };
Field
Default
Meaning
consentStates
Allowed only
Subscribe to active conversations with one of these consent states. An empty list selects none.
includeWelcomes
true
Subscribe to this installation’s welcome topic.
includeSyncGroups
false
Include device-sync groups.
includeCommits
false
Include commits and proposals on subscribed group topics.
An enabled or disabled override takes priority over consentStates. Reset with
NotificationOverride.Default, .default, or "default". Overrides cannot
enable a group after this installation leaves it. Device-sync groups follow
includeSyncGroups and have no per-conversation override. The value in
conversation state reports the local rule. It does not confirm backend
subscription delivery or active membership.
The SDK updates subscriptions after consent, membership, and key changes. It
uploads sender-filter keys and renews registrations automatically while its task
runner is active. An update is asynchronous; it does not make messaging wait for
push registration. A lost wake can delay an update until the next periodic sync, which normally runs
at least once an hour.
Sender filtering can miss during key rotation or an epoch boundary, so an app
must still suppress its own messages when it displays notifications.
Local state is Disabled, Enabled, or Failed(error) on Kotlin; .disabled,
.enabled, or .failed(error) on Swift; and a union with kind set to
"disabled", "enabled", or "failed" on Node. Failed state contains a
NotificationFailure enum value. Kotlin uses values such as
CHANNEL_NOT_CONFIGURED, Swift uses .channelNotConfigured, and Node uses
"channelNotConfigured".
Terminal failures are permission denied, invalid argument, out of range,
unimplemented, and channel not configured. Operation errors use the matching
public error variants and codes: PermissionDenied, InvalidArgument,
OutOfRange, Unimplemented, and ChannelNotConfigured.
These failures stop notification work. Correct the cause and call enableNotifications
again. TaskRunnerDisabled rejects enable without storing a config. Keep the task runner enabled if the app uses notifications. Configure it through
ClientOptions.workers; Node names this worker kind "taskRunner".
Other registration failures leave the local state enabled so the task can retry.
ResourceExhausted waits for a change to the desired subscriptions before it
retries additions. Each notification request has a 30-second limit.
Disable keeps the local recipient identity and conversation overrides. It stays
disabled even if unregister fails. In that case the backend registration remains
until expiry. A later enable reuses the identity from the same database.
Every push identifies an envelope. It contains no encrypted message, message
content, inbox ID, or recipient secret.
{
"topic": "AAECAwQFBgcICQoLDA0ODxA=",
"sequence_id": "9007199254740993"
}
topic is standard base64 of the backend wire topic. Decode it before routing.
A group topic starts with 0x00 and contains a 16-byte group ID. A welcome topic
starts with 0x01 and contains a 32-byte installation key. The group ID is the
hex encoding of the bytes after the kind byte. Do not pass the base64 text to a
legacy string-topic lookup.
sequence_id is decimal text. Keep it as text or an exact integer such as
JavaScript bigint. Do not convert it to a JavaScript number or use it alone
as a message-delivery cursor.
APNs adds "aps": { "content-available": 1 }. FCM puts topic and
sequence_id in the message’s data object. HTTPS adds recipient_id as hex.
The HTTPS payload contains only topic, sequence_id, and recipient_id.
HTTPS requests carry the Standard Webhooks headers
webhook-id, webhook-timestamp, and webhook-signature. Verify the signature
over the exact body bytes before parsing, check the timestamp, and reject
replayed webhook IDs.
The app owns notification reception and display. It must obtain the provider
token, request the required OS permissions, and install its background handler.
APNs sends a background notification, not an alert with display text. FCM sends
a data message. A notification does not contain encrypted envelope bytes.
Use the SDK sync methods to fetch and process the envelopes.
Open the correct XMTP installation and its local database in the handler.
Validate the payload and decode the topic. A webhook receiver first verifies
the signature. Treat a push as a sync hint, not as proof of a message.
Fetch and process pending welcomes. For a group topic, find the group by its
decoded ID, then sync it to fetch and decrypt the envelopes. Kotlin uses
client.catchUpToLive(timeoutMs = 5_000uL) for a bounded sync. Swift uses
try await client.catchUpToLive(timeoutMs: 5_000). Check the returned
summary’s completed and failed fields. Node also exposes
catchUpToLive; the example below uses explicit welcome and conversation sync.
Read decoded messages from the local database. Check consent, sender, and
the app’s display policy. Suppress messages already displayed, including
notifications that repeat after a backend restart.
Show the permitted notification and complete the OS background callback.
If the OS budget ends or the envelope is not available yet, keep work for
the next allowed background run or foreground sync.
The snippet returns local messages for the app to filter. It does not implement
signature verification, display deduplication, OS scheduling, or user-interface
updates. A welcome can reveal an existing DM, so it need not produce an alert.
Push delivery can be delayed, dropped, or repeated. Normal message sync remains
the source of message state.
Different installations can create separate groups for the same DM. The SDK
presents these groups as one visible DM. It checks every underlying active group
for notification subscriptions, using each group’s consent and override rules. A DM lookup can return a different group ID as the underlying
groups converge. Use the peer inbox ID for app state that belongs to the DM.
Do not show a new-conversation alert when a welcome only adds a duplicate DM.
This is a breaking change. XMTPPush, the generated push-service stubs,
getPushTopics, and allPushTopics are removed. Apps must call
enableNotifications to register again. Previous subscriptions and
per-conversation notification choices are not migrated. The new default selects
only allowed conversations.
Rewrite receivers for the payload above. The old encryptedMessage field and
string routing topics are absent. The backend operator must decommission the
old notification server and remove its registrations. The new
disableNotifications cannot remove registrations from that server; old pushes
can continue until the operator completes cleanup.