docs/platform/integrations/push/apns.mdx
This guide explains how to configure and use the Apple Push Notification Service (APNS) with Novu to deliver push notifications to iOS devices. It outlines:
Before sending notifications, APNS must be configured with the correct credentials from your Apple Developer account. Novu uses these credentials to securely authenticate with Apple’s servers.
Apple provides two authentication options for connecting to APNS:
.p12 certificate.p8 keyNovu supports both, but this guide focuses on the .p8 token-based approach, which is recommended for most production setups.
To generate the required credentials, use an Apple Developer account with an Admin role. Follow Apple’s official steps to create and download a private .p8 key.
The following identifiers are also needed for integration:
Once the Apple credentials are available, you can add them in Novu’s Integration Store.
.p8 file.After configuration, APNS can be used in any Novu workflow that includes a Push step. The process involves registering device tokens for subscribers and then triggering workflows to deliver messages.
Each subscriber (user) must have one or more device tokens registered to receive push notifications. Tokens can be added or updated through Novu’s API using the Update Subscriber Credentials endpoint.
<Tabs> <Tab title="Node.js"> ```typescript import { Novu } from '@novu/api'; import { ChatOrPushProviderEnum } from "@novu/api/models/components";const novu = new Novu({ secretKey: "<NOVU_SECRET_KEY>" });
await novu.subscribers.credentials.update( { providerId: ChatOrPushProviderEnum.Apns, integrationIdentifier: "string", credentials: { deviceTokens: ["token1", "token2", "token3"] }, }, "subscriberId" );
</Tab>
<Tab title="Python">
```python
import os
import novu_py
from novu_py import Novu
with Novu(secret_key=os.getenv("NOVU_SECRET_KEY", "")) as novu:
novu.subscribers.credentials.update(
subscriber_id="subscriberId",
update_subscriber_channel_request_dto={
"provider_id": novu_py.ChatOrPushProviderEnum.APNS,
"credentials": {"deviceTokens": ["token1", "token2", "token3"]},
"integration_identifier": "string",
},
)
novugo "github.com/novuhq/novu-go"
"github.com/novuhq/novu-go/models/components"
)
s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))
res, err := s.Subscribers.Credentials.Update(context.Background(), "subscriberId", components.UpdateSubscriberChannelRequestDto{ ProviderID: components.ChatOrPushProviderEnumApns, IntegrationIdentifier: novugo.String("string"), Credentials: components.ChannelCredentials{ DeviceTokens: []string{"token1", "token2", "token3"}, }, }, nil)
</Tab>
<Tab title="PHP">
```php
use novu;
use novu\Models\Components;
$sdk = novu\Novu::builder()->setSecurity('<NOVU_SECRET_KEY>')->build();
$sdk->subscribersCredentials->update(
subscriberId: 'subscriberId',
updateSubscriberChannelRequestDto: new Components\UpdateSubscriberChannelRequestDto(
providerId: Components\ChatOrPushProviderEnum::Apns,
integrationIdentifier: 'string',
credentials: new Components\ChannelCredentials(
deviceTokens: ['token1', 'token2', 'token3'],
),
),
);
var sdk = new NovuSDK(secretKey: "<NOVU_SECRET_KEY>");
await sdk.Subscribers.Credentials.UpdateAsync( subscriberId: "subscriberId", updateSubscriberChannelRequestDto: new UpdateSubscriberChannelRequestDto() { ProviderId = ChatOrPushProviderEnum.Apns, IntegrationIdentifier = "string", Credentials = new ChannelCredentials() { DeviceTokens = new List<string> { "token1", "token2", "token3" }, }, });
</Tab>
<Tab title="Java">
```java
import co.novu.Novu;
import co.novu.models.components.*;
import java.util.List;
Novu novu = Novu.builder().secretKey("<NOVU_SECRET_KEY>").build();
novu.subscribers().credentials().update()
.subscriberId("subscriberId")
.body(UpdateSubscriberChannelRequestDto.builder()
.providerId(ChatOrPushProviderEnum.APNS)
.integrationIdentifier("string")
.credentials(ChannelCredentials.builder()
.deviceTokens(List.of("token1", "token2", "token3"))
.build())
.build())
.call();
Once subscribers’ devices are registered, push notifications are delivered through workflows that include a Push step. A workflow can be triggered using the Novu SDK or API.
<Tabs> <Tab title="Node.js"> ```typescript import { Novu } from '@novu/api';const novu = new Novu({ secretKey: "<NOVU_SECRET_KEY>" });
await novu.trigger({ workflowId: "workflowId", to: { subscriberId: "SUBSCRIBER_ID", }, payload: {}, });
</Tab>
<Tab title="Python">
```python
import os
import novu_py
from novu_py import Novu
with Novu(secret_key=os.getenv("NOVU_SECRET_KEY", "")) as novu:
novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto(
workflow_id="workflowId",
to={"subscriber_id": "SUBSCRIBER_ID"},
payload={},
))
novugo "github.com/novuhq/novu-go"
"github.com/novuhq/novu-go/models/components"
)
s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))
res, err := s.Trigger(context.Background(), components.TriggerEventRequestDto{ WorkflowID: "workflowId", To: components.CreateToSubscriberPayloadDto(components.SubscriberPayloadDto{ SubscriberID: "SUBSCRIBER_ID", }), Payload: map[string]any{}, }, nil)
</Tab>
<Tab title="PHP">
```php
use novu;
use novu\Models\Components;
$sdk = novu\Novu::builder()->setSecurity('<NOVU_SECRET_KEY>')->build();
$sdk->trigger(
triggerEventRequestDto: new Components\TriggerEventRequestDto(
workflowId: 'workflowId',
to: new Components\SubscriberPayloadDto(subscriberId: 'SUBSCRIBER_ID'),
payload: [],
),
);
var sdk = new NovuSDK(secretKey: "<NOVU_SECRET_KEY>");
await sdk.TriggerAsync(triggerEventRequestDto: new TriggerEventRequestDto() { WorkflowId = "workflowId", To = To.CreateSubscriberPayloadDto(new SubscriberPayloadDto() { SubscriberId = "SUBSCRIBER_ID" }), Payload = new Dictionary<string, object>(), });
</Tab>
<Tab title="Java">
```java
import co.novu.Novu;
import co.novu.models.components.*;
import java.util.Map;
Novu novu = Novu.builder().secretKey("<NOVU_SECRET_KEY>").build();
novu.trigger()
.body(TriggerEventRequestDto.builder()
.workflowId("workflowId")
.to(To2.of(SubscriberPayloadDto.builder().subscriberId("SUBSCRIBER_ID").build()))
.payload(Map.of())
.build())
.call();
Novu supports an overrides field that lets you attach APNS-specific payload parameters when triggering a workflow.
The overrides field supports all APNS Notification payload values. Here is an example:
const novu = new Novu({ secretKey: "<NOVU_SECRET_KEY>" });
await novu.trigger({ workflowId: "workflowId", to: { subscriberId: "subscriberId", }, payload: { "abc": "def" }, overrides: { "apns": { "payload": { "aps": { "notification": { "title": "Test", "body": "Test push" }, "data": { "key": "value" } } } } }, });
</Tab>
<Tab title="Python">
```python
import os
import novu_py
from novu_py import Novu
with Novu(secret_key=os.getenv("NOVU_SECRET_KEY", "")) as novu:
novu.trigger(trigger_event_request_dto=novu_py.TriggerEventRequestDto(
workflow_id="workflowId",
to={"subscriber_id": "subscriberId"},
payload={
"abc": "def"
},
overrides={
"apns": {
"payload": {
"aps": {
"notification": {
"title": "Test",
"body": "Test push"
},
"data": {
"key": "value"
}
}
}
}
},
))
novugo "github.com/novuhq/novu-go"
"github.com/novuhq/novu-go/models/components"
)
s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))
res, err := s.Trigger(context.Background(), components.TriggerEventRequestDto{ WorkflowID: "workflowId", To: components.CreateToSubscriberPayloadDto(components.SubscriberPayloadDto{ SubscriberID: "subscriberId", }), Payload: map[string]any{ "abc": "def", }, Overrides: map[string]any{ "apns": map[string]any{ "payload": map[string]any{ "aps": map[string]any{ "notification": map[string]any{ "title": "Test", "body": "Test push", }, "data": map[string]any{ "key": "value", }, }, }, }, }, }, nil)
</Tab>
<Tab title="PHP">
```php
use novu;
use novu\Models\Components;
$sdk = novu\Novu::builder()->setSecurity('<NOVU_SECRET_KEY>')->build();
$sdk->trigger(
triggerEventRequestDto: new Components\TriggerEventRequestDto(
workflowId: 'workflowId',
to: new Components\SubscriberPayloadDto(subscriberId: 'subscriberId'),
payload: ['abc' => 'def'],
overrides: [
'apns' => [
'payload' => [
'aps' => [
'notification' => [
'title' => 'Test',
'body' => 'Test push',
],
'data' => [
'key' => 'value',
],
],
],
],
],
),
);
var sdk = new NovuSDK(secretKey: "<NOVU_SECRET_KEY>");
await sdk.TriggerAsync(triggerEventRequestDto: new TriggerEventRequestDto() { WorkflowId = "workflowId", To = To.CreateSubscriberPayloadDto(new SubscriberPayloadDto() { SubscriberId = "subscriberId" }), Payload = new Dictionary<string, object>() { { "abc", "def" }, }, Overrides = new Overrides() { Push = new Dictionary<string, object>() { { "payload", new Dictionary<string, object>() { { "aps", new Dictionary<string, object>() { { "notification", new Dictionary<string, object>() { { "title", "Test" }, { "body", "Test push" }, } }, { "data", new Dictionary<string, object>() { { "key", "value" }, } }, } }, } }, }, }, });
</Tab>
<Tab title="Java">
```java
import co.novu.Novu;
import co.novu.models.components.*;
import java.util.Map;
Novu novu = Novu.builder().secretKey("<NOVU_SECRET_KEY>").build();
novu.trigger()
.body(TriggerEventRequestDto.builder()
.workflowId("workflowId")
.to(To2.of(SubscriberPayloadDto.builder().subscriberId("subscriberId").build()))
.payload(Map.of("abc", "def"))
.overrides(TriggerEventRequestDtoOverrides.builder()
.additionalProperties(Map.of("apns", Map.of(
"payload", Map.of(
"aps", Map.of(
"notification", Map.of("title", "Test", "body", "Test push"),
"data", Map.of("key", "value"))))))
.build())
.build())
.call();