The XMTP backend sends push notifications through APNs, FCM, or an HTTPS webhook.
The Android, iOS, and Node SDKs register the installation 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 example apps do not include push registration or a notification receiver.
The console module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
A global console instance configured to write to process.stdout and
process.stderr. The global console can be used without importing the node:console module.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
Prints to stderr with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()).
constcode=5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr
If formatting elements (e.g. %d) are not found in the first string then
util.inspect() is called on each argument and the
resulting string values are concatenated. See util.format()
for more 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 type
equal to "apns", "fcm", or "http" on Node.
APNs and FCM need a token. HTTP needs an HTTPS url and a random 32-byte
signingKey. The receiver must keep the same signing key to verify requests.
Bytes use ByteArray, Data, and Uint8Array, respectively.
const
constchannel:NotificationChannel
channel:
typeNotificationChannel= {
type:"apns";
token:string;
} | {
type:"fcm";
token:string;
} | {
type:"http";
url:string;
signingKey:Uint8Array;
}
Delivery channel for this installation.
NotificationChannel= {
type: "http"
type: "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 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 hourly sync.
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 state set to
"disabled", "enabled", or "failed" on Node. Failed state contains a
NotificationError with a stable code.
Terminal codes have the prefix NotificationError::: PermissionDenied,
InvalidArgument, OutOfRange, Unimplemented, or ChannelNotConfigured.
They stop notification work. Correct the cause and call enableNotifications
again. TaskRunnerDisabled rejects enable without storing a config. Node apps
must not disable WorkerKind.TaskRunner if they use notifications.
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 the bytes accepted by
processMessage or fromWelcome.
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. On mobile,
client.catchUpToLive(timeoutMs: ...) provides a bounded sync; use the
language’s argument syntax. On Node, use the calls below.
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.
@param ― options - Optional filtering and pagination options
@returns ― Promise that resolves with an array of decoded messages
messages();
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 and automatically subscribes to every
matching group. 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.