Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

One Signal Laravel Package

ladumor/one-signal

Laravel package for sending push notifications via OneSignal. Offers a simple API to target devices, users, and segments, create and manage notifications, and integrate OneSignal into Laravel apps with minimal setup. Ideal for web and mobile push campaigns.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin HTTP wrapper around OneSignal's /api/v1/notifications endpoint, exposed as a Laravel facade. It does not handle device registration, SDK setup, certificates, or webhooks — those stay in your mobile/web app. Treat it purely as "fire a push from the backend."

1. Install and configure

composer require ladumor/one-signal
php artisan vendor:publish --tag=config   # publishes config/one-signal.php

Add to .env:

ONE_SIGNAL_APP_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ONE_SIGNAL_REST_API_KEY=os_v2_app_...   # or the legacy REST API Key

The REST API key is not the App ID and is not the app's "API Key" from the dashboard's older UI — get it from Settings → Keys & IDs → REST API Key. This is the single most common first-day failure.

2. Where to look first

Before writing code, read vendor/ladumor/one-signal/src/ (there are only a handful of files) and config/one-signal.php. The public surface is small and the facade method signatures shift between minor versions — the README on GitHub can lag behind the installed tag, and with 0 dependents there's no ecosystem blog posts to fall back on. The source is the documentation.

3. First use case: welcome push on signup

use Ladumor\OneSignal\OneSignal;

// after the device's OneSignal SDK has been initialised and you've
// persisted the player_id (or set external_user_id) on your user record
OneSignal::sendNotificationToUser(
    'Welcome aboard!',
    $user->onesignal_player_id,
    url: config('app.url') . '/dashboard',
);

The realistic day-to-day loop is: user signs up → the app SDK gives you a player_id (or you set external_user_id server-side) → you store it on users → you push from jobs, observers, or scheduled commands.

Implementation Patterns

Wrap the facade in your own service. The facade is static and hard to substitute. Put it behind an interface so tests and future changes (e.g. migrating to OneSignal's v2 API) don't ripple through the codebase:

final class PushService
{
    public function toUser(User $user, string $title, string $body, array $data = []): bool
    {
        if (! $user->onesignal_player_id) {
            return false; // user hasn't opted in / registered a device
        }

        $response = OneSignal::sendNotificationToUser($body, $user->onesignal_player_id, null, $data, $title);

        // the wrapper returns the decoded OneSignal response, it does not throw
        if (! empty($response['errors'])) {
            Log::warning('OneSignal push failed', ['user' => $user->id, 'response' => $response]);
            return false;
        }
        return isset($response['id']);
    }
}

Targeting patterns you'll actually use

Need Call style
Broadcast / announcement sendNotificationToAll
Transactional (order shipped, DM) sendNotificationToUser with player id
Preference-based (topics, locale, tier) sendNotificationUsingTags
Segment created in dashboard sendNotificationToSegment
Specific device (testing) sendNotificationToDevice

Always push from a queued job.

final class SendOrderShippedPush implements ShouldQueue
{
    public int $tries = 3;
    public array $backoff = [10, 60, 300];

    public function __construct(public int $orderId) {}

    public function handle(PushService $push): void
    {
        $order = Order::with('user')->findOrFail($this->orderId);
        $push->toUser($order->user, 'Shipped', "Order #{$order->id} is on its way", [
            'type'    => 'order_shipped',
            'order_id' => (string) $order->id,
        ]);
    }
}

Network latency to onesignal.com is 200–800 ms. Never do it inline in a web request.

Use data for routing, not deep links in url alone. The data array round-trips to the app SDK's notification-open handler and is what you'll use to navigate to a screen. Reserve url for web push and universal-link fallbacks.

Chunk bulk sends. OneSignal caps include_player_ids / include_external_user_ids at 2,000 per request. If you're notifying "everyone who favourited this listing", collect IDs and array_chunk($ids, 2000) in a loop job — the package won't do it for you.

Multi-app / staging isolation. Instead of hard-coding keys in config, override at runtime for test sends:

config(['one-signal.app_id' => config('services.onesignal.staging_app_id')]);

Better still: pin a services.onesignal.* set per environment and never send a real broadcast from local. Create an internal segment (e.g. testers) in the OneSignal dashboard and make that the default target outside production.

Integrate with Laravel's Notification system by writing a custom channel rather than calling the facade in controllers. That keeps push alongside mail/database channels and gives you via() control per user:

public function via($notifiable) { return $notifiable->push_enabled ? [PushChannel::class] : []; }

Gotchas and Tips

The wrapper does not throw on API errors. You get back OneSignal's JSON: {"id": "", "recipients": 0, "errors": ["All included players are not subscribed"]}. errors means failure; warnings can be benign. Always inspect the response — a silent false/empty array is the default behaviour and the reason people think pushes "randomly stopped".

"All included players are not subscribed" almost always means stale player_ids. Users uninstall, revoke permission, or the SDK reissues a new ID. Persist player_id on every app launch (a tiny PATCH /users endpoint in your app), and prune on this error rather than retrying.

config:cache bites you. env() calls in config/one-signal.php are resolved at cache-build time. If you cache config in a deploy with no .env values present, you'll ship null credentials and get 401s only in production. Prefer real entries in config/services.php and env() only there.

Mocking the facade in tests. OneSignal::shouldReceive(...) works if the facade has been registered, but the wrapper talks to Guzzle/Http, so Http::fake() is often cleaner and doesn't require the provider:

Http::fake(['onesignal.com/*' => Http::response(['id' => 'abc', 'recipients' => 1], 200)]);

OneSignal API v2 migration looms. The package targets the legacy /api/v1 endpoint with the Authorization: Basic {REST_API_KEY} header. OneSignal's newer apps use app-scoped keys (os_v2_app_...) with /api/v2. If your app was created recently, verify which key type the wrapper sends — you may need to point the base URL at v2 yourself or bypass the package for that call. This is the main reason to keep the facade behind your own service.

Payload limit is ~4 KB total. Long contents plus large data plus base64 images will silently truncate or reject. Keep data IDs-only (never embed full models), and reference images by URL (big_picture, ios_attachments) rather than inflating the payload.

Scheduling format is fussy. send_after accepts "Sep 24 2015 14:00:00 GMT-0700" or ISO 8601 — not a Unix timestamp. Pass an explicit offset or use CarbonImmutable::toIso8601String(); timezone guessing produces off-by-hours deliveries that are painful to trace.

No webhooks, no analytics, no template helpers. Delivery/open stats and template management require direct HTTP calls. Add a Http::withToken(...) client in your app for those rather than fighting the wrapper.

Pin the version. 123 stars, zero dependents, single maintainer: there's no crowd to catch regressions. Use "ladumor/one-signal": "^4.0" style constraints and read the diff before bumping majors — helper signatures have historically changed.

Debug with Laravel's HTTP events, not dd() inside vendor code. Http::globalRequestMiddleware / Http::globalResponseMiddleware or a Log::debug around your PushService call will show the exact include_* filters and OneSignal's verbatim errors.

Extension point: the package has no events. Fire your own PushSent/PushFailed events from PushService if you want metrics, alerting, or fallback to APNs/FCM direct on failure.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource