> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novu.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Push

> Connect push providers like FCM, APNS, and OneSignal to Novu. Manage subscriber device tokens and deliver mobile and web push notifications through workflows.

Push providers are the services that deliver notifications your subscribers' devices, which can be delivered through mobile, desktop, or web. Each provider must be set up individually in the Novu dashboard.

Novu provides a unified integration layer that connects your workflows to push providers. Once your push provider is configured, Novu automatically handles message delivery, routing each notification through the correct provider without requiring extra setup.

You can also manage multiple integrations even for the same provider in one place, giving you full flexibility and centralized control over your push notifications.

## Key features

* **Multi-provider support**: Integrate with providers like Firebase Cloud Messaging (FCM), OneSignal, or Apple Push Notification Service (APNS).
* **Unified delivery**: Streamline your push notifications with a single API for mobile and web platforms.
* **Device management**: Keep subscriber device tokens in sync using just-in-time or manual updates.

## How push works in Novu

Here is the step-by-step process for sending a push notification with Novu:

<Steps>
  <Step>
    ### Add a push provider integration

    Start by adding a push channel provider integration in the **Integration Store** on your Novu dashboard.
    You can connect one or more integrations for the same provider, each with its own credentials and environment configuration.
  </Step>

  <Step>
    ### Add the Push channel to your workflow

    Next, add a Push step to a workflow. This step defines when and how a push message should be sent as part of your notification logic.
  </Step>

  <Step>
    ### Store device tokens for your subscribers

    Each subscriber in Novu must have a valid device token specific to the push provider integration.
    These tokens are stored in the subscriber’s profile and can be added or updated using the SDK or API.
    Novu uses these tokens to route messages to the correct device when a workflow runs.
  </Step>

  <Step>
    ### Trigger the workflow

    Trigger the workflow by sending an event from your application code.
    Novu automatically resolves the subscriber, selects active push provider integrations and their device tokens, and delivers the message through the configured integrations. See [Integration conditions](/platform/integrations/integration-conditions).
  </Step>
</Steps>

## Managing push device tokens

To send push notifications, each subscriber must have one or more device tokens for each push provider integration associated with their profile.

These tokens are unique identifiers that help push notification providers deliver messages to the correct devices. Each provider has its own method for obtaining device tokens, refer to your provider's guide to learn how to generate the device tokens.

<Note>
  Each subscriber channel supports a maximum of **100 device tokens**. Attempts to create or update credentials beyond this limit will be rejected. Remove unused or stale tokens to stay within the limit. See [Platform Limits](/platform/developer/limits) for all system limits.
</Note>

<Note>
  The code examples in this section use fcm. For other providers, replace `ChatOrPushProviderEnum.Fcm` with the correct enum for your provider, such as `ChatOrPushProviderEnum.Apns` or `ChatOrPushProviderEnum.Expo`.
</Note>

### Add push device tokens

Novu offers two ways of adding device tokens to a subscriber's profile:

* Just in time - inline with triggering the workflow
* Ahead of trigger - before triggering the workflow

#### Just in time

You can pass the device tokens in the `channels` array of the subscriber field when triggering a workflow. Novu automatically updates the subscriber's profile with these tokens before sending the message. Here is an example with FCM:

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    import { Novu } from '@novu/api';
    import { ChatOrPushProviderEnum } from "@novu/api/models/components";

    const novu = new Novu({
      secretKey: "<NOVU_SECRET_KEY>",
      // Required if using EU region
      // serverURL: "https://eu.api.novu.co",
    });

    await novu.trigger({
      workflowId: "workflowId",
      to: {
        subscriberId: "subscriberId",
        channels: [
          {
            providerId: ChatOrPushProviderEnum.Fcm,
            credentials: {
              deviceTokens: ["token-1", "token-2"],
            }
          },
        ],
      },
      payload: {},
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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",
                "channels": [
                    {
                        "provider_id": novu_py.ChatOrPushProviderEnum.FCM,
                        "credentials": {
                            "device_tokens": ["token-1", "token-2"],
                        },
                    },
                ],
            },
            payload={},
        ))
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"
        "os"

        novugo "github.com/novuhq/novu-go"
        "github.com/novuhq/novu-go/models/components"
    )

    s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))

    _, err := s.Trigger(context.Background(), components.TriggerEventRequestDto{
        WorkflowID: "workflowId",
        To: components.CreateToSubscriberPayloadDto(components.SubscriberPayloadDto{
            SubscriberID: "subscriberId",
            Channels: []components.SubscriberChannelDto{
                {
                    ProviderID: components.SubscriberChannelDtoProviderIdFcm,
                    Credentials: components.ChannelCredentialsDto{
                        DeviceTokens: []string{"token-1", "token-2"},
                    },
                },
            },
        }),
        Payload: map[string]any{},
    }, nil)
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    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',
                channels: [
                    new Components\SubscriberChannelDto(
                        providerId: Components\SubscriberChannelDtoProviderId::Fcm,
                        credentials: new Components\ChannelCredentialsDto(
                            deviceTokens: ['token-1', 'token-2'],
                        ),
                    ),
                ],
            ),
            payload: [],
        ),
    );
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using Novu;
    using Novu.Models.Components;
    using System.Collections.Generic;

    var sdk = new NovuSDK(secretKey: "<NOVU_SECRET_KEY>");

    await sdk.TriggerAsync(triggerEventRequestDto: new TriggerEventRequestDto() {
        WorkflowId = "workflowId",
        To = To.CreateSubscriberPayloadDto(new SubscriberPayloadDto() {
            SubscriberId = "subscriberId",
            Channels = new List<SubscriberChannelDto>() {
                new SubscriberChannelDto() {
                    ProviderId = SubscriberChannelDtoProviderId.Fcm,
                    Credentials = new ChannelCredentialsDto() {
                        DeviceTokens = new List<string>() { "token-1", "token-2" },
                    },
                },
            },
        }),
        Payload = new Dictionary<string, object>(),
    });
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import co.novu.Novu;
    import co.novu.models.components.*;
    import java.util.List;
    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")
                .channels(List.of(
                    SubscriberChannelDto.builder()
                        .providerId(SubscriberChannelDtoProviderId.FCM)
                        .credentials(ChannelCredentialsDto.builder()
                            .deviceTokens(List.of("token-1", "token-2"))
                            .build())
                        .build()))
                .build()))
            .payload(Map.of())
            .build())
        .call();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -L -X POST 'https://api.novu.co/v1/events/trigger' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
    -d '{
        "name": "workflowId",
        "to": {
            "subscriberId": "subscriberId",
            "channels": [
                {
                    "providerId": "fcm",
                    "credentials": {
                        "deviceTokens": ["token-1", "token-2"]
                    }
                }
            ]
        },
        "payload": {}
    }'
    ```
  </Tab>
</Tabs>

#### Ahead of trigger

You can add device tokens for a subscriber using the [Update provider credentials API](/api-reference/subscribers/update-provider-credentials) before triggering the workflow.

This method is useful when you manage device registration outside of your workflow triggers, for example, after a user logs in.

<Note>
  The update credentials endpoint sets the `deviceTokens` array to the value you send for the target provider (and `integrationIdentifier` when specified). To preserve existing tokens when registering a new one, first fetch the subscriber, then send the merged list. The same pattern applies to removing a single token, see [Remove a specific token](#remove-a-specific-token).
</Note>

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    import { Novu } from '@novu/api';
    import { ChatOrPushProviderEnum } from "@novu/api/models/components";

    const novu = new Novu({ secretKey: "YOUR_SECRET_KEY_HERE" });

    const result = await novu.subscribers.credentials.update({
      providerId: ChatOrPushProviderEnum.Fcm,
      credentials: {
        deviceTokens: [
          "token1",
          "token2",
        ],
      },
    }, "subscriberId");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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.FCM,
                "credentials": {
                    "device_tokens": ["token1", "token2"],
                },
            },
        )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"
        "os"

        novugo "github.com/novuhq/novu-go"
        "github.com/novuhq/novu-go/models/components"
    )

    s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))

    _, err := s.Subscribers.Credentials.Update(context.Background(), "subscriberId", components.UpdateSubscriberChannelRequestDto{
        ProviderID: components.ChatOrPushProviderEnumFcm,
        Credentials: components.ChannelCredentials{
            DeviceTokens: []string{"token1", "token2"},
        },
    }, nil)
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    use novu;
    use novu\Models\Components;

    $sdk = novu\Novu::builder()
        ->setSecurity('<NOVU_SECRET_KEY>')
        ->build();

    $sdk->subscribers->updateCredentials(
        subscriberId: 'subscriberId',
        updateSubscriberChannelRequestDto: new Components\UpdateSubscriberChannelRequestDto(
            providerId: Components\ChatOrPushProviderEnum::Fcm,
            credentials: new Components\ChannelCredentials(
                deviceTokens: ['token1', 'token2'],
            ),
        ),
    );
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using Novu;
    using Novu.Models.Components;
    using System.Collections.Generic;

    var sdk = new NovuSDK(secretKey: "<NOVU_SECRET_KEY>");

    await sdk.Subscribers.UpdateCredentialsAsync(
        subscriberId: "subscriberId",
        updateSubscriberChannelRequestDto: new UpdateSubscriberChannelRequestDto() {
            ProviderId = ChatOrPushProviderEnum.Fcm,
            Credentials = new ChannelCredentials() {
                DeviceTokens = new List<string>() { "token1", "token2" },
            },
        }
    );
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import co.novu.Novu;
    import co.novu.models.components.*;
    import java.util.List;

    Novu novu = Novu.builder()
        .secretKey("<NOVU_SECRET_KEY>")
        .build();

    novu.subscribers().updateCredentials()
        .subscriberId("subscriberId")
        .body(UpdateSubscriberChannelRequestDto.builder()
            .providerId(ChatOrPushProviderEnum.FCM)
            .credentials(ChannelCredentials.builder()
                .deviceTokens(List.of("token1", "token2"))
                .build())
            .build())
        .call();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -L -X PUT 'https://api.novu.co/v1/subscribers/<SUBSCRIBER_ID>/credentials' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
    -d '{
      "providerId": "fcm",
      "credentials": {
        "deviceTokens" : [
          "token1",
          "token2"
        ]
      }
    }'
    ```
  </Tab>
</Tabs>

### Add device tokens for specific integrations

Novu supports multiple active integrations per provider for the push channel. For example, you can have more than one active FCM integration at a time, one for android and one for web app.

By default, device tokens are stored for the most recently created integration. To store device tokens for a specific integration, you must use the `integrationIdentifier` field. This is the identifier for the integration, which you can find in the Novu dashboard.

<img src="https://mintcdn.com/novu-c5de82d9/J466heAwyEbvOhEB/images/channels-and-providers/push/integration-identifier.png?fit=max&auto=format&n=J466heAwyEbvOhEB&q=85&s=9bfc55c592a572662c97222b5232a9c3" alt="Integration identifier" width="2880" height="1624" data-path="images/channels-and-providers/push/integration-identifier.png" />

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    import { Novu } from '@novu/api';
    import { ChatOrPushProviderEnum } from "@novu/api/models/components";

    const novu = new Novu({ secretKey: "YOUR_SECRET_KEY_HERE" });

    const result = await novu.subscribers.credentials.update({
      providerId: ChatOrPushProviderEnum.Fcm,
      // Use integrationIdentifier to store device tokens for a specific integration
      integrationIdentifier: "fcm-MnGLxp8uy",
      credentials: {
        deviceTokens: [
          "token1",
          "token2",
        ],
      },
    }, "subscriberId");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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.FCM,
                "integration_identifier": "fcm-MnGLxp8uy",
                "credentials": {
                    "device_tokens": ["token1", "token2"],
                },
            },
        )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"
        "os"

        novugo "github.com/novuhq/novu-go"
        "github.com/novuhq/novu-go/models/components"
    )

    integrationIdentifier := "fcm-MnGLxp8uy"

    s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))

    _, err := s.Subscribers.Credentials.Update(context.Background(), "subscriberId", components.UpdateSubscriberChannelRequestDto{
        ProviderID: components.ChatOrPushProviderEnumFcm,
        IntegrationIdentifier: &integrationIdentifier,
        Credentials: components.ChannelCredentials{
            DeviceTokens: []string{"token1", "token2"},
        },
    }, nil)
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    use novu;
    use novu\Models\Components;

    $sdk = novu\Novu::builder()
        ->setSecurity('<NOVU_SECRET_KEY>')
        ->build();

    $sdk->subscribers->updateCredentials(
        subscriberId: 'subscriberId',
        updateSubscriberChannelRequestDto: new Components\UpdateSubscriberChannelRequestDto(
            providerId: Components\ChatOrPushProviderEnum::Fcm,
            integrationIdentifier: 'fcm-MnGLxp8uy',
            credentials: new Components\ChannelCredentials(
                deviceTokens: ['token1', 'token2'],
            ),
        ),
    );
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using Novu;
    using Novu.Models.Components;
    using System.Collections.Generic;

    var sdk = new NovuSDK(secretKey: "<NOVU_SECRET_KEY>");

    await sdk.Subscribers.UpdateCredentialsAsync(
        subscriberId: "subscriberId",
        updateSubscriberChannelRequestDto: new UpdateSubscriberChannelRequestDto() {
            ProviderId = ChatOrPushProviderEnum.Fcm,
            IntegrationIdentifier = "fcm-MnGLxp8uy",
            Credentials = new ChannelCredentials() {
                DeviceTokens = new List<string>() { "token1", "token2" },
            },
        }
    );
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import co.novu.Novu;
    import co.novu.models.components.*;
    import java.util.List;

    Novu novu = Novu.builder()
        .secretKey("<NOVU_SECRET_KEY>")
        .build();

    novu.subscribers().updateCredentials()
        .subscriberId("subscriberId")
        .body(UpdateSubscriberChannelRequestDto.builder()
            .providerId(ChatOrPushProviderEnum.FCM)
            .integrationIdentifier("fcm-MnGLxp8uy")
            .credentials(ChannelCredentials.builder()
                .deviceTokens(List.of("token1", "token2"))
                .build())
            .build())
        .call();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -L -X PUT 'https://api.novu.co/v1/subscribers/<SUBSCRIBER_ID>/credentials' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
    -d '{
      "providerId": "fcm",
      "credentials": {
        "deviceTokens" : [
          "token1",
          "token2"
        ]
      },
      "integrationIdentifier": "fcm-MnGLxp8uy"
    }'
    ```
  </Tab>
</Tabs>

### Remove device tokens

To remove device token(s) from subscriber credentials, follow this process:

When a subscriber logs out of a device, remove that device token from the subscriber's credentials. This prevents another subscriber who may log in to the same device from receiving notifications meant for the previous subscriber.

<Note>
  Handle token removal, such as during a user logout event on the server-side, to prevent stale or invalid tokens from causing failed deliveries.
</Note>

#### Automatic removal of stale device tokens

<Note>
  Novu automatically removes invalid, stale or expired device tokens from a subscribers' profile and then sends the failure details via `message.failed` [webhook event](/platform/developer/webhooks/event-types#message-events). This ensures that notifications are only sent to valid and active devices. This feature is currently supported for FCM and Expo providers. If you are looking for other providers to support this feature, reach out to us at [support@novu.co](mailto:support@novu.co)
</Note>

#### Remove a specific token

To remove device tokens from subscriber credentials, follow this process:

1. Fetch the subscriber and read `deviceTokens` from the matching entry in `channels`.
2. Filter out the token you want to remove.
3. Update the subscriber with the new, filtered list.

<Note>
  Device tokens live on `channels[]`, not a top-level `credentials` map. When you have multiple active integrations for the same provider, match and update with `integrationIdentifier` so you do not replace tokens on a different integration. See [Add device tokens for specific integrations](#add-device-tokens-for-specific-integrations).
</Note>

<Tabs>
  <Tab title="Node.js">
    ```ts theme={null}
    import { Novu } from '@novu/api';
    import { ChatOrPushProviderEnum } from "@novu/api/models/components";

    const novu = new Novu({
      secretKey: "<NOVU_SECRET_KEY>",
    });

    const subscriberId = "subscriberId";
    const providerId = ChatOrPushProviderEnum.Fcm;
    const integrationIdentifier = "fcm-MnGLxp8uy";

    // 1. Fetch current credentials from channels
    const { result: subscriber } = await novu.subscribers.retrieve(subscriberId);
    const currentTokens =
      subscriber.channels?.find(
        (channel) =>
          channel.providerId === providerId &&
          channel.integrationIdentifier === integrationIdentifier
      )?.credentials?.deviceTokens ?? [];

    // 2. Remove the specific token
    const filteredTokens = currentTokens.filter(token => token !== "token_to_remove");

    // 3. Update subscriber for the same integration
    await novu.subscribers.credentials.update(
      {
        providerId,
        integrationIdentifier,
        credentials: {
          deviceTokens: filteredTokens,
        },
      },
      subscriberId
    );
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import novu_py
    from novu_py import Novu

    subscriber_id = "subscriberId"
    provider_id = novu_py.ChatOrPushProviderEnum.FCM
    integration_identifier = "fcm-MnGLxp8uy"

    with Novu(secret_key=os.getenv("NOVU_SECRET_KEY", "")) as novu:
        # 1. Fetch current credentials from channels
        response = novu.subscribers.retrieve(subscriber_id=subscriber_id)
        subscriber = response.subscriber_response_dto
        current_tokens = next(
            (
                channel.credentials.device_tokens or []
                for channel in (subscriber.channels or [])
                if channel.provider_id == provider_id
                and channel.integration_identifier == integration_identifier
            ),
            [],
        )

        # 2. Remove the specific token
        filtered_tokens = [token for token in current_tokens if token != "token_to_remove"]

        # 3. Update subscriber for the same integration
        novu.subscribers.credentials.update(
            subscriber_id=subscriber_id,
            update_subscriber_channel_request_dto={
                "provider_id": provider_id,
                "integration_identifier": integration_identifier,
                "credentials": {
                    "device_tokens": filtered_tokens,
                },
            },
        )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"
        "os"

        novugo "github.com/novuhq/novu-go"
        "github.com/novuhq/novu-go/models/components"
    )

    subscriberID := "subscriberId"
    providerID := components.ChatOrPushProviderEnumFcm
    integrationIdentifier := "fcm-MnGLxp8uy"

    s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))

    // 1. Fetch current credentials from channels
    res, err := s.Subscribers.Retrieve(context.Background(), subscriberID, nil)
    if err != nil {
        panic(err)
    }

    currentTokens := []string{}
    if res.SubscriberResponseDto != nil {
        for _, channel := range res.SubscriberResponseDto.Channels {
            if channel.ProviderID == providerID &&
                channel.IntegrationIdentifier != nil &&
                *channel.IntegrationIdentifier == integrationIdentifier {
                currentTokens = channel.Credentials.DeviceTokens
                break
            }
        }
    }

    // 2. Remove the specific token
    filteredTokens := []string{}
    for _, token := range currentTokens {
        if token != "token_to_remove" {
            filteredTokens = append(filteredTokens, token)
        }
    }

    // 3. Update subscriber for the same integration
    _, err = s.Subscribers.Credentials.Update(context.Background(), subscriberID, components.UpdateSubscriberChannelRequestDto{
        ProviderID: providerID,
        IntegrationIdentifier: &integrationIdentifier,
        Credentials: components.ChannelCredentials{
            DeviceTokens: filteredTokens,
        },
    }, nil)
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    use novu;
    use novu\Models\Components;

    $subscriberId = 'subscriberId';
    $providerId = Components\ChatOrPushProviderEnum::Fcm;
    $integrationIdentifier = 'fcm-MnGLxp8uy';

    $sdk = novu\Novu::builder()
        ->setSecurity('<NOVU_SECRET_KEY>')
        ->build();

    // 1. Fetch current credentials from channels
    $response = $sdk->subscribers->retrieve(subscriberId: $subscriberId);
    $currentTokens = [];

    foreach ($response->subscriberResponseDto->channels ?? [] as $channel) {
        if (
            $channel->providerId === $providerId &&
            ($channel->integrationIdentifier ?? null) === $integrationIdentifier
        ) {
            $currentTokens = $channel->credentials->deviceTokens ?? [];
            break;
        }
    }

    // 2. Remove the specific token
    $filteredTokens = array_values(array_filter(
        $currentTokens,
        fn (string $token): bool => $token !== 'token_to_remove',
    ));

    // 3. Update subscriber for the same integration
    $sdk->subscribers->updateCredentials(
        subscriberId: $subscriberId,
        updateSubscriberChannelRequestDto: new Components\UpdateSubscriberChannelRequestDto(
            providerId: $providerId,
            integrationIdentifier: $integrationIdentifier,
            credentials: new Components\ChannelCredentials(
                deviceTokens: $filteredTokens,
            ),
        ),
    );
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using Novu;
    using Novu.Models.Components;
    using System.Collections.Generic;
    using System.Linq;

    var sdk = new NovuSDK(secretKey: "<NOVU_SECRET_KEY>");

    var subscriberId = "subscriberId";
    var providerId = ChatOrPushProviderEnum.Fcm;
    var integrationIdentifier = "fcm-MnGLxp8uy";

    // 1. Fetch current credentials from channels
    var response = await sdk.Subscribers.RetrieveAsync(subscriberId: subscriberId);
    var currentTokens = response.SubscriberResponseDto.Channels?
        .FirstOrDefault(channel =>
            channel.ProviderId == providerId &&
            channel.IntegrationIdentifier == integrationIdentifier
        )?.Credentials?.DeviceTokens ?? new List<string>();

    // 2. Remove the specific token
    var filteredTokens = currentTokens
        .Where(token => token != "token_to_remove")
        .ToList();

    // 3. Update subscriber for the same integration
    await sdk.Subscribers.UpdateCredentialsAsync(
        subscriberId: subscriberId,
        updateSubscriberChannelRequestDto: new UpdateSubscriberChannelRequestDto() {
            ProviderId = providerId,
            IntegrationIdentifier = integrationIdentifier,
            Credentials = new ChannelCredentials() {
                DeviceTokens = filteredTokens,
            },
        }
    );
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import co.novu.Novu;
    import co.novu.models.components.*;
    import java.util.Collections;
    import java.util.List;
    import java.util.stream.Collectors;

    Novu novu = Novu.builder()
        .secretKey("<NOVU_SECRET_KEY>")
        .build();

    String subscriberId = "subscriberId";
    String integrationIdentifier = "fcm-MnGLxp8uy";

    // 1. Fetch current credentials from channels
    var subscriber = novu.subscribers().retrieve()
        .subscriberId(subscriberId)
        .call()
        .subscriberResponseDto()
        .orElseThrow();

    List<String> currentTokens = subscriber.channels().orElse(Collections.emptyList()).stream()
        .filter(channel -> channel.providerId() == ChatOrPushProviderEnum.FCM
            && integrationIdentifier.equals(channel.integrationIdentifier().orElse(null)))
        .findFirst()
        .map(channel -> channel.credentials().deviceTokens().orElse(Collections.emptyList()))
        .orElse(Collections.emptyList());

    // 2. Remove the specific token
    List<String> filteredTokens = currentTokens.stream()
        .filter(token -> !token.equals("token_to_remove"))
        .collect(Collectors.toList());

    // 3. Update subscriber for the same integration
    novu.subscribers().updateCredentials()
        .subscriberId(subscriberId)
        .body(UpdateSubscriberChannelRequestDto.builder()
            .providerId(ChatOrPushProviderEnum.FCM)
            .integrationIdentifier(integrationIdentifier)
            .credentials(ChannelCredentials.builder()
                .deviceTokens(filteredTokens)
                .build())
            .build())
        .call();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    # 1. Fetch current credentials (read deviceTokens from channels[] for the target integration)
    curl -L -X GET 'https://api.novu.co/v2/subscribers/subscriberId' \
    -H 'Accept: application/json' \
    -H 'Authorization: ApiKey <NOVU_SECRET_KEY>'

    # 2. Remove the specific token from that channel's deviceTokens, then update credentials
    curl -L -X PUT 'https://api.novu.co/v1/subscribers/subscriberId/credentials' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
    -d '{
      "providerId": "fcm",
      "integrationIdentifier": "fcm-MnGLxp8uy",
      "credentials": {
        "deviceTokens": ["remaining-token-1", "remaining-token-2"]
      }
    }'
    ```
  </Tab>
</Tabs>

#### Remove all tokens

To remove all device tokens for a provider, or for a specific integration, update the credentials with an empty array.

<Tabs>
  <Tab title="Node.js">
    ```tsx theme={null}
    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.Fcm,
        credentials: {
          deviceTokens: [],
        },
      },
      "subscriberId"
    );
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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.FCM,
                "credentials": {
                    "device_tokens": [],
                },
            },
        )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "context"
        "os"

        novugo "github.com/novuhq/novu-go"
        "github.com/novuhq/novu-go/models/components"
    )

    s := novugo.New(novugo.WithSecurity(os.Getenv("NOVU_SECRET_KEY")))

    _, err := s.Subscribers.Credentials.Update(context.Background(), "subscriberId", components.UpdateSubscriberChannelRequestDto{
        ProviderID: components.ChatOrPushProviderEnumFcm,
        Credentials: components.ChannelCredentials{
            DeviceTokens: []string{},
        },
    }, nil)
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    use novu;
    use novu\Models\Components;

    $sdk = novu\Novu::builder()
        ->setSecurity('<NOVU_SECRET_KEY>')
        ->build();

    $sdk->subscribers->updateCredentials(
        subscriberId: 'subscriberId',
        updateSubscriberChannelRequestDto: new Components\UpdateSubscriberChannelRequestDto(
            providerId: Components\ChatOrPushProviderEnum::Fcm,
            credentials: new Components\ChannelCredentials(
                deviceTokens: [],
            ),
        ),
    );
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={null}
    using Novu;
    using Novu.Models.Components;
    using System.Collections.Generic;

    var sdk = new NovuSDK(secretKey: "<NOVU_SECRET_KEY>");

    await sdk.Subscribers.UpdateCredentialsAsync(
        subscriberId: "subscriberId",
        updateSubscriberChannelRequestDto: new UpdateSubscriberChannelRequestDto() {
            ProviderId = ChatOrPushProviderEnum.Fcm,
            Credentials = new ChannelCredentials() {
                DeviceTokens = new List<string>(),
            },
        }
    );
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import co.novu.Novu;
    import co.novu.models.components.*;
    import java.util.List;

    Novu novu = Novu.builder()
        .secretKey("<NOVU_SECRET_KEY>")
        .build();

    novu.subscribers().updateCredentials()
        .subscriberId("subscriberId")
        .body(UpdateSubscriberChannelRequestDto.builder()
            .providerId(ChatOrPushProviderEnum.FCM)
            .credentials(ChannelCredentials.builder()
                .deviceTokens(List.of())
                .build())
            .build())
        .call();
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -L -X PUT 'https://api.novu.co/v1/subscribers/subscriberId/credentials' \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
    -d '{
      "providerId": "fcm",
      "credentials": {
        "deviceTokens": []
      }
    }'
    ```
  </Tab>
</Tabs>

## Provider content overrides

The Push step editor covers subject and body. When you need provider-specific fields — sounds, data payloads, platform options, or routing keys — configure a **provider content override** on the Push step in the workflow editor.

An override is a JSON object that Novu deep-merges over the payload the provider builds from the step's subject and body. It is saved on the step, so it is versioned and promoted between environments with the rest of the workflow, and it applies to every trigger without any change to your trigger call.

<Note>
  Provider content overrides in the workflow editor are rolling out gradually and may not be available on your Push step yet. [Trigger overrides](/platform/integrations/trigger-overrides) work regardless.
</Note>

### Expo is schema-backed

Expo overrides are validated against the [Expo Push message](https://docs.expo.dev/push-notifications/sending-notifications) request format (minus `to`, which Novu fills from the subscriber's device tokens). The editor autocompletes supported field names, validates the JSON, and flags unsupported fields.

<Card title="Configure Expo overrides" icon="smartphone" href="/platform/integrations/push/expo-push#configure-expo-overrides">
  Schema-backed Expo override fields, body fallback, and title behavior.
</Card>

### FCM is schema-backed

FCM overrides are validated against the FCM HTTP v1 [`Message`](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#Message) shape, plus Novu's multicast `tokens` key. The editor autocompletes supported field names, validates the JSON, and flags unsupported fields. Routing keys (`token`, `tokens`, `topic`, `condition`) are allowed in step content overrides — see [Configure FCM overrides](/platform/integrations/push/fcm#configure-fcm-overrides) for exclusive-or rules, Liquid, fan-out, and trigger precedence.

<Card title="Configure FCM overrides" icon="smartphone" href="/platform/integrations/push/fcm#configure-fcm-overrides">
  Schema-backed FCM override fields, `notification.body` fallback, and routing in content or trigger overrides.
</Card>

### Every other push provider is a raw passthrough

For APNS, OneSignal, Pushpad, Push Webhook, Pusher Beams, and AppIO, the override is an unvalidated escape hatch. Whatever JSON object you save is merged into that provider's outbound payload as-is.

That means:

* **No validation.** Novu does not check field names, types, or nesting. A typo reaches the provider's API unchanged. Depending on the provider it either fails the delivery, which you will see in the **Activity feed**, or is silently ignored.
* **No autocomplete.** There is no schema to suggest from.
* **The shape is the provider's contract, not Novu's.** Write the object against the provider's own API reference. If the provider changes its API, your override has to change with it.
* **No key blocklist.** Provider-specific routing keys in the override are allowed and take effect.

Use this when a provider supports something Novu's step editor does not expose, and test with a real trigger before relying on it.

### Liquid and precedence

Override values may contain [Liquid](/platform/workflow/add-notification-content/personalize-content#apply-logic-with-liquidjs) templates, which Novu compiles at send time.

When the same field is set both on the step and at trigger time, the trigger value wins, and arrays replace rather than merge. Precedence from lowest to highest: persisted dashboard override, then trigger workflow-level `overrides.providers`, then trigger step-level. See [provider override scopes](/platform/integrations/trigger-overrides#provider-override-scopes).

`_passthrough.body` sits outside this ordering. Wherever it appears, it is merged last and takes priority over every layer above — including provider routing keys such as FCM's `topic`. See [routing with `_passthrough`](/platform/integrations/push/fcm#routing-with-_passthrough).

## Supported providers

Novu supports the following providers:

<Columns cols={2}>
  <Card title="Firebase Cloud Messaging (FCM)" href="/platform/integrations/push/fcm">
    Learn how to use the FCM provider to send push notifications using Novu.
  </Card>

  <Card title="Expo Push" href="/platform/integrations/push/expo-push">
    Learn how to use the Expo Push provider to send push notifications using Novu.
  </Card>

  <Card title="Apple Push Notification Service (APNS)" href="/platform/integrations/push/apns">
    Learn how to use the APNS provider to send push notifications using Novu.
  </Card>

  <Card title="OneSignal" href="/platform/integrations/push/onesignal">
    Learn how to use the OneSignal provider to send push notifications using Novu.
  </Card>

  <Card title="Push Webhook" href="/platform/integrations/push/push-webhook">
    Learn how to use the Push Webhook provider to send push notifications using Novu.
  </Card>

  <Card title="Pusher Beams" href="/platform/integrations/push/pusher-beams">
    Learn how to use the Pusher Beams provider to send push notifications using Novu.
  </Card>

  <Card title="Pushpad" href="/platform/integrations/push/pushpad">
    Learn how to use the Pushpad provider to send push notifications using Novu.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.