Flow Studio
Push Notifications
Configuring the SendPushNotification node — device tokens, notification title and body, custom data payloads, and FCM / APNs credential management.
Node Configuration
{
"nodeType": "SendPushNotification",
"name": "pushApprovalAlert",
"config": {
"credentialId": 22,
"provider": "fcm",
"deviceToken": "$output.fetchUserDevice.fcmToken",
"title": "Approval Required",
"body": "Invoice $output.createInvoice.invoiceNumber needs your sign-off",
"data": {
"executionId": "$context.executionId",
"invoiceId": "$output.createInvoice.invoiceId",
"deepLink": "acme://approvals/$output.createInvoice.invoiceId"
},
"badge": 1,
"sound": "default",
"priority": "high",
"ttlSeconds": 86400
}
}
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | int | Yes | FCM service account key JSON or APNs p8 key, stored via ICredentialResolver |
provider | fcm | apns | Yes | Push provider. Determines adapter used. |
deviceToken | string / expr | Yes | FCM registration token or APNs device token for the target device. |
title | string / expr | Yes | Notification title shown in device notification tray. |
body | string / expr | Yes | Notification body text. |
data | object | No | Custom key-value data payload delivered to the app (not shown in tray). Values are evaluated expressions. |
badge | int / expr | No | iOS badge count. Ignored by FCM on Android. |
sound | string | No | Notification sound. Use "default" or a custom sound file name bundled in the app. |
priority | high | normal | No | FCM message priority. high wakes the device immediately. |
ttlSeconds | int | No | Time-to-live in seconds. If device is offline longer than TTL, message is dropped. |
FCM vs APNs
| Aspect | FCM (Firebase Cloud Messaging) | APNs (Apple Push) |
|---|---|---|
| Platform | Android (also iOS via FCM bridge) | iOS, macOS, watchOS |
| Credential type | Service account JSON key | p8 key file + Team ID + Key ID + Bundle ID |
| Token format | FCM registration token (long string) | 64-char hex device token |
| Priority | high / normal | 10 (immediate) / 5 (power-save) |
| Data-only message | Omit notification key — data only | Set content-available: 1 |
PushChannelAdapter — FCM
public class PushChannelAdapter : IMessagingChannelAdapter
{
public string ChannelType => "Push";
public async Task<MessagingResult> SendAsync(
MessagingMessage message,
MessagingConfig config,
CancellationToken ct)
{
if (config.Provider == "apns")
return await SendViaApns(message, config, ct);
// FCM path
var serviceAccountJson = await _credentials.GetPasswordAsync(config.CredentialId, ct);
var credential = GoogleCredential
.FromJson(serviceAccountJson)
.CreateScoped("https://www.googleapis.com/auth/firebase.messaging");
var fcmMessage = new Message
{
Token = message.To,
Notification = new Notification { Title = message.Title, Body = message.Body },
Data = message.Data?.ToDictionary(k => k.Key, k => k.Value?.ToString() ?? ""),
Android = new AndroidConfig
{
Priority = config.Priority == "high"
? Priority.High : Priority.Normal,
Ttl = TimeSpan.FromSeconds(config.TtlSeconds ?? 86400)
}
};
var app = FirebaseApp.Create(new AppOptions { Credential = credential });
var messageId = await FirebaseMessaging.DefaultInstance.SendAsync(fcmMessage, ct);
return new MessagingResult
{
MessageId = messageId,
Channel = "push",
Recipient = message.To,
SentAt = DateTimeOffset.UtcNow,
Status = "sent"
};
}
}
Data-only notifications: To send a silent background push (no UI notification) for in-app processing, omit
title and body and include only a data payload. On FCM Android, this is a "data message". On APNs, combine with content-available: 1.
Token staleness: Device tokens expire when a user reinstalls the app or resets their device. Store tokens in your entity system and listen for FCM's
NotRegistered error to clean up stale tokens automatically.