Prerequisites
- A Pusher Beams instance
- Your Pusher Beams Instance ID and Secret Key
Setup
1
Get your Pusher Beams credentials
In Pusher, open the dashboard and copy your Beams instance ID and secret key.
2
Configure in Courier
Open the in Courier, enter your instance ID and secret key, then click “Save.”
Profile requirements
Device tokens live on the user profile rather than on the send, so one profile can hold tokens for several devices and every push provider reads them from the same place. covers the model. Pusher Beams sends to interests or to users, so the profile you send to needs apusherBeams object with at least one mode: web, apns, or fcm. Store it once with , which merges into the profile and creates it if it does not exist:
- Interests
- Users
Everyone subscribed to the interest gets the notification.
const profile = await client.profiles.create('user_123', {
profile: {
pusherBeams: {
interests: [
'interest-1',
'interest-2',
],
mode: [
'apns',
'fcm',
],
},
},
});
profile = client.profiles.create(
user_id="user_123",
profile={
"pusherBeams": {
"interests": [
"interest-1",
"interest-2",
],
"mode": [
"apns",
"fcm",
],
},
},
)
curl --request POST \
--url https://api.courier.com/profiles/user_123 \
--header "Authorization: Bearer $COURIER_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"profile": {
"pusherBeams": {
"interests": [
"interest-1",
"interest-2"
],
"mode": [
"apns",
"fcm"
]
}
}
}'
profile = courier.profiles.create(
"user_123",
profile: {
pusherBeams: {
interests: ["interest-1", "interest-2"],
mode: ["apns", "fcm"]
}
}
)
profile, err := client.Profiles.New(
context.TODO(),
"user_123",
courier.ProfileNewParams{
Profile: map[string]any{
"pusherBeams": map[string]any{
"interests": []any{"interest-1", "interest-2"},
"mode": []any{"apns", "fcm"},
},
},
},
)
ProfileCreateParams params = ProfileCreateParams.builder()
.userId("user_123")
.profile(ProfileCreateParams.Profile.builder()
.putAdditionalProperty("pusherBeams", JsonValue.from(java.util.Map.of(
"interests", java.util.List.of("interest-1", "interest-2"),
"mode", java.util.List.of("apns", "fcm"))))
.build())
.build();
ProfileCreateResponse profile = client.profiles().create(params);
$profile = $client->profiles->create('user_123', profile: [
'pusherBeams' => [
'interests' => ['interest-1', 'interest-2'],
'mode' => ['apns', 'fcm'],
],
]);
ProfileCreateParams parameters = new()
{
UserID = "user_123",
Profile = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"pusherBeams": {
"interests": [
"interest-1",
"interest-2"
],
"mode": [
"apns",
"fcm"
]
}
}
"""
),
};
var profile = await client.Profiles.Create(parameters);
courier profiles create \
--api-key "$COURIER_API_KEY" \
--user-id user_123 \
--profile '{"pusherBeams":{"interests":["interest-1","interest-2"],"mode":["apns","fcm"]}}'
With Courier MCP, save the Pusher Beams interests interest-1 and interest-2 on user_123.
Beams resolves each id against the users it has registered.
const profile = await client.profiles.create('user_123', {
profile: {
pusherBeams: {
userIds: [
'user-id-1',
'user-id-2',
],
mode: [
'apns',
'fcm',
],
},
},
});
profile = client.profiles.create(
user_id="user_123",
profile={
"pusherBeams": {
"userIds": [
"user-id-1",
"user-id-2",
],
"mode": [
"apns",
"fcm",
],
},
},
)
curl --request POST \
--url https://api.courier.com/profiles/user_123 \
--header "Authorization: Bearer $COURIER_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"profile": {
"pusherBeams": {
"userIds": [
"user-id-1",
"user-id-2"
],
"mode": [
"apns",
"fcm"
]
}
}
}'
profile = courier.profiles.create(
"user_123",
profile: {
pusherBeams: {
userIds: ["user-id-1", "user-id-2"],
mode: ["apns", "fcm"]
}
}
)
profile, err := client.Profiles.New(
context.TODO(),
"user_123",
courier.ProfileNewParams{
Profile: map[string]any{
"pusherBeams": map[string]any{
"userIds": []any{"user-id-1", "user-id-2"},
"mode": []any{"apns", "fcm"},
},
},
},
)
ProfileCreateParams params = ProfileCreateParams.builder()
.userId("user_123")
.profile(ProfileCreateParams.Profile.builder()
.putAdditionalProperty("pusherBeams", JsonValue.from(java.util.Map.of(
"userIds", java.util.List.of("user-id-1", "user-id-2"),
"mode", java.util.List.of("apns", "fcm"))))
.build())
.build();
ProfileCreateResponse profile = client.profiles().create(params);
$profile = $client->profiles->create('user_123', profile: [
'pusherBeams' => [
'userIds' => ['user-id-1', 'user-id-2'],
'mode' => ['apns', 'fcm'],
],
]);
ProfileCreateParams parameters = new()
{
UserID = "user_123",
Profile = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"pusherBeams": {
"userIds": [
"user-id-1",
"user-id-2"
],
"mode": [
"apns",
"fcm"
]
}
}
"""
),
};
var profile = await client.Profiles.Create(parameters);
courier profiles create \
--api-key "$COURIER_API_KEY" \
--user-id user_123 \
--profile '{"pusherBeams":{"userIds":["user-id-1","user-id-2"],"mode":["apns","fcm"]}}'
With Courier MCP, save the Pusher Beams user ids user-id-1 and user-id-2 on user_123.
user_id and Courier resolves the address.
For a one-off with no stored profile, pass it inline instead: "to": { "pusherBeams": { "interests": ["interest-1"], "mode": ["apns"] } }.
Send by user id
The call in every language, and the rest of the profile object.
Send to a recipient
- Send to user id
- Send to
pusherBeams
Courier reads
pusherBeams off the saved profile, so preferences apply and the value can change without touching this code.const { requestId } = await courier.send.message({
message: {
to: {
user_id: "user_123",
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
},
});
response = client.send.message(
message={
"to": {
"user_id": "user_123",
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"user_id": "user_123"
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40"
}
}'
response = courier.send_.message(
message: {
to: {
user_id: "user_123"
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40"
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
UserID: courier.String("user_123"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().userId("user_123").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'to' => [
'user_id' => 'user_123',
],
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { UserID = "user_123" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"user_id": "user_123"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40
With Courier MCP, send my template to user_123 by email.
Pass it inline instead and nothing is stored. Swap this
to object into the call on the other tab.const { requestId } = await courier.send.message({
message: {
// A provider recipient is a profile field, so it sits outside the typed union.
to: {
pusherBeams: {
interests: ["interest-1", "interest-2"],
mode: ["apns", "fcm"],
},
} as any,
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
},
});
response = client.send.message(
message={
"to": {
"pusherBeams": {
"interests": ["interest-1", "interest-2"],
"mode": ["apns", "fcm"],
},
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"pusherBeams": {
"interests": [
"interest-1",
"interest-2"
],
"mode": [
"apns",
"fcm"
]
}
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40"
}
}'
response = courier.send_.message(
message: {
to: {
pusherBeams: {
interests: ["interest-1", "interest-2"],
mode: ["apns", "fcm"]
}
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40"
}
)
// A provider recipient is a profile field, so it sits outside the typed union.
to := param.Override[shared.UserRecipientParam](
json.RawMessage(`{"pusherBeams": {"interests": ["interest-1", "interest-2"], "mode": ["apns", "fcm"]}}`),
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{OfUserRecipient: &to},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
},
})
// A provider recipient is a profile field, so it sits outside the typed union.
UserRecipient to = UserRecipient.builder()
.putAdditionalProperty("pusherBeams", JsonValue.from(Map.of(
"interests", List.of("interest-1", "interest-2"),
"mode", List.of("apns", "fcm"))))
.build();
client.send().message(SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(to)
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.build())
.build());
$response = $client->send->message(
message: [
'to' => [
'pusherBeams' => [
'interests' => ['interest-1', 'interest-2'],
'mode' => ['apns', 'fcm'],
],
],
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
],
);
// A provider recipient is a profile field, so it sits outside the typed union.
UserRecipient to = UserRecipient.FromRawUnchecked(
JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""{"pusherBeams": {"interests": ["interest-1", "interest-2"], "mode": ["apns", "fcm"]}}"""
)
);
var response = await client.Send.Message(new() { Message = new() { To = to, Template = "nt_01kx4h2jdafq8bk9aftxak4b40" } });
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"pusherBeams": {"interests": ["interest-1", "interest-2"], "mode": ["apns", "fcm"]}}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40
With Courier MCP, send my template to the Pusher Beams interests interest-1 and interest-2.
Overrides
covers the two levels and which one wins. lists the fields every push provider takes. A provider override changes the request body Courier sends to Pusher Beams, or swaps the instance ID and secret key.Body overrides
This example replaces the title and body:const { requestId } = await courier.send.message({
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
pusherBeams: {
interests: [
"interest-1",
],
mode: [
"fcm",
],
},
},
providers: {
"pusher-beams": {
override: {
body: {
title: "Custom Title",
body: "Custom body text",
},
},
},
},
},
});
response = client.send.message(
message={
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"pusherBeams": {
"interests": [
"interest-1",
],
"mode": [
"fcm",
],
},
},
"providers": {
"pusher-beams": {
"override": {
"body": {
"title": "Custom Title",
"body": "Custom body text",
},
},
},
},
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"pusherBeams": {
"interests": [
"interest-1"
],
"mode": [
"fcm"
]
}
},
"providers": {
"pusher-beams": {
"override": {
"body": {
"title": "Custom Title",
"body": "Custom body text"
}
}
}
}
}
}'
response = courier.send_.message(
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
pusherBeams: {
interests: [
"interest-1"
],
mode: [
"fcm"
]
}
},
providers: {
"pusher-beams": {
override: {
body: {
title: "Custom Title",
body: "Custom body text"
}
}
}
}
}
)
// This provider addresses the recipient with fields outside the typed
// UserRecipient model, so pass the recipient as raw JSON.
to := param.Override[shared.UserRecipientParam](json.RawMessage(`{
"pusherBeams": {
"interests": [
"interest-1"
],
"mode": [
"fcm"
]
}
}`))
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{OfUserRecipient: &to},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Providers: shared.MessageProvidersParam{
"pusher-beams": shared.MessageProvidersTypeParam{
Override: map[string]any{
"body": map[string]any{
"title": "Custom Title",
"body": "Custom body text",
},
},
},
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
// This provider addresses the recipient with fields outside the
// typed UserRecipient model, so pass them as additional properties.
.to(UserRecipient.builder()
.putAdditionalProperty("pusherBeams", JsonValue.from(java.util.Map.of(
"interests", java.util.List.of(
"interest-1"
),
"mode", java.util.List.of(
"fcm"
)
)))
.build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.providers(MessageProviders.builder()
.putAdditionalProperty("pusher-beams", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
"body", java.util.Map.of(
"title", "Custom Title",
"body", "Custom body text"
)
))))
.build())
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
'to' => [
'pusherBeams' => [
'interests' => [
'interest-1',
],
'mode' => [
'fcm',
],
],
],
'providers' => [
'pusher-beams' => [
'override' => [
'body' => [
'title' => 'Custom Title',
'body' => 'Custom body text',
],
],
],
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
// This provider addresses the recipient with fields outside the
// typed UserRecipient model, so build it from raw JSON.
To = UserRecipient.FromRawUnchecked(
JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"pusherBeams": {
"interests": [
"interest-1"
],
"mode": [
"fcm"
]
}
}
"""
)
),
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Providers = new Dictionary<string, MessageProvidersType>()
{
{
"pusher-beams",
new()
{
Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"body": {
"title": "Custom Title",
"body": "Custom body text"
}
}
"""
),
}
},
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"pusherBeams": {"interests": ["interest-1"], "mode": ["fcm"]}}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.providers '{"pusher-beams": {"override": {"body": {"title": "Custom Title", "body": "Custom body text"}}}}'
With Courier MCP, send my template to these Pusher Beams interests with the body overridden.
Courier sets only
title and body. Overrides merge into that alert object and go out in every mode you select, so mode-specific shapes like fcm.notification are ignored.Config overrides
Swap the instance ID or secret key at send time:const { requestId } = await courier.send.message({
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
pusherBeams: {
interests: [
"interest-1",
],
mode: [
"fcm",
],
},
},
providers: {
"pusher-beams": {
override: {
config: {
instanceId: "RUNTIME_INSTANCE_ID",
secretKey: "RUNTIME_SECRET_KEY",
},
},
},
},
},
});
response = client.send.message(
message={
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"pusherBeams": {
"interests": [
"interest-1",
],
"mode": [
"fcm",
],
},
},
"providers": {
"pusher-beams": {
"override": {
"config": {
"instanceId": "RUNTIME_INSTANCE_ID",
"secretKey": "RUNTIME_SECRET_KEY",
},
},
},
},
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"pusherBeams": {
"interests": [
"interest-1"
],
"mode": [
"fcm"
]
}
},
"providers": {
"pusher-beams": {
"override": {
"config": {
"instanceId": "RUNTIME_INSTANCE_ID",
"secretKey": "RUNTIME_SECRET_KEY"
}
}
}
}
}
}'
response = courier.send_.message(
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
pusherBeams: {
interests: [
"interest-1"
],
mode: [
"fcm"
]
}
},
providers: {
"pusher-beams": {
override: {
config: {
instanceId: "RUNTIME_INSTANCE_ID",
secretKey: "RUNTIME_SECRET_KEY"
}
}
}
}
}
)
// This provider addresses the recipient with fields outside the typed
// UserRecipient model, so pass the recipient as raw JSON.
to := param.Override[shared.UserRecipientParam](json.RawMessage(`{
"pusherBeams": {
"interests": [
"interest-1"
],
"mode": [
"fcm"
]
}
}`))
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{OfUserRecipient: &to},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Providers: shared.MessageProvidersParam{
"pusher-beams": shared.MessageProvidersTypeParam{
Override: map[string]any{
"config": map[string]any{
"instanceId": "RUNTIME_INSTANCE_ID",
"secretKey": "RUNTIME_SECRET_KEY",
},
},
},
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
// This provider addresses the recipient with fields outside the
// typed UserRecipient model, so pass them as additional properties.
.to(UserRecipient.builder()
.putAdditionalProperty("pusherBeams", JsonValue.from(java.util.Map.of(
"interests", java.util.List.of(
"interest-1"
),
"mode", java.util.List.of(
"fcm"
)
)))
.build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.providers(MessageProviders.builder()
.putAdditionalProperty("pusher-beams", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
"config", java.util.Map.of(
"instanceId", "RUNTIME_INSTANCE_ID",
"secretKey", "RUNTIME_SECRET_KEY"
)
))))
.build())
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
'to' => [
'pusherBeams' => [
'interests' => [
'interest-1',
],
'mode' => [
'fcm',
],
],
],
'providers' => [
'pusher-beams' => [
'override' => [
'config' => [
'instanceId' => 'RUNTIME_INSTANCE_ID',
'secretKey' => 'RUNTIME_SECRET_KEY',
],
],
],
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
// This provider addresses the recipient with fields outside the
// typed UserRecipient model, so build it from raw JSON.
To = UserRecipient.FromRawUnchecked(
JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"pusherBeams": {
"interests": [
"interest-1"
],
"mode": [
"fcm"
]
}
}
"""
)
),
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Providers = new Dictionary<string, MessageProvidersType>()
{
{
"pusher-beams",
new()
{
Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"config": {
"instanceId": "RUNTIME_INSTANCE_ID",
"secretKey": "RUNTIME_SECRET_KEY"
}
}
"""
),
}
},
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"pusherBeams": {"interests": ["interest-1"], "mode": ["fcm"]}}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.providers '{"pusher-beams": {"override": {"config": {"instanceId": "RUNTIME_INSTANCE_ID", "secretKey": "RUNTIME_SECRET_KEY"}}}}'
With Courier MCP, send my template to these Pusher Beams interests with a different instance id.
Provider details
pusher-beams
routing.channels instead is supported, and sends through just this provider.
Send to a specific provider
When that is worth doing, and what you give up: failover, channel priority, and providers you add later.