Migrating from Pusher to Mawjly
Mawjly is a drop-in replacement for Pusher Channels. Because Mawjly speaks the same wire protocol (Pusher V1) that every official Pusher SDK targets, migration is a config-only change in your application — no code rewrites, no new SDKs, no breaking changes.
Most teams complete the swap in under 10 minutes per environment.
This guide also exists in Arabic. Same content, RTL layout, Arabic code annotations.
Why migrate
You’re a candidate for Mawjly if at least one of these is true:
- Your users are in the Middle East or GCC. Pusher’s nearest cluster is in Frankfurt or Mumbai, adding ~150-200ms to every event. Mawjly’s Riyadh cluster delivers sub-50ms median latency to Saudi Arabia, the UAE, Egypt, and Qatar.
- Your Pusher bill is becoming a noticeable line item. Mawjly is 30% below Pusher’s monthly list price at every paid tier — and because Mawjly gives 2 months free on annual billing while Pusher charges 12× monthly with no annual discount, annual customers save up to 42%.
- You’ve been frustrated by Pusher’s debug experience. Mawjly’s live debug console streams every event from your app in real time, with channel/event filtering and one-click event replay. Drop the
console.loglines — read your traffic from the dashboard. - You need an Arabic UI for your team or compliance reviewers. Mawjly’s dashboard, invoices, and admin are fully bilingual EN/AR with proper RTL.
- Your data residency requires an MENA-hosted provider. Mawjly runs in Saudi Arabia and is committed to PDPL-aligned operations.
If none of these apply to you, Pusher is a fine product — stay there.
The 5-minute migration
There are exactly four configuration values you change. Nothing else.
| Setting | Pusher value | Mawjly value |
|---|---|---|
wsHost (or host) | ws-mt1.pusher.com (or your Pusher cluster) | ws-sa.mawjly.com |
cluster | mt1 (or your Pusher cluster) | sa |
key | <your_pusher_app_key> | <your_mawjly_app_key> |
secret | <your_pusher_secret> (server-side only) | <your_mawjly_secret> |
Your app_id is also different per provider, but it’s only used in server-side calls and the credentials section of your dashboard.
Below are exact config swaps for every officially-supported Pusher SDK.
JavaScript / TypeScript (browser)
// Before — Pusher
import Pusher from 'pusher-js';
const pusher = new Pusher('YOUR_KEY', {
cluster: 'mt1',
});// After — Mawjly
import Pusher from 'pusher-js';
const pusher = new Pusher('YOUR_MAWJLY_KEY', {
wsHost: 'ws-sa.mawjly.com',
cluster: 'sa',
forceTLS: true,
});That’s it. subscribe(), bind(), presence- channels, and encrypted channels all behave identically.
React (with the same pusher-js SDK)
If you wrap pusher-js in a React hook or context, only the constructor changes. Your hook code is untouched:
// Before
const pusher = new Pusher(KEY, { cluster: 'mt1' });
// After
const pusher = new Pusher(KEY, {
wsHost: 'ws-sa.mawjly.com',
cluster: 'sa',
forceTLS: true,
});PHP — pusher/pusher-php-server
// Before
$pusher = new Pusher\Pusher('KEY', 'SECRET', 'APP_ID', [
'cluster' => 'mt1',
'useTLS' => true,
]);// After
$pusher = new Pusher\Pusher('KEY', 'SECRET', 'APP_ID', [
'host' => 'ws-sa.mawjly.com',
'cluster' => 'sa',
'useTLS' => true,
]);Laravel + pusher-php-server (broadcasting)
In config/broadcasting.php:
// Before
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
],
],// After
'pusher' => [
'driver' => 'pusher',
'key' => env('MAWJLY_APP_KEY'),
'secret' => env('MAWJLY_APP_SECRET'),
'app_id' => env('MAWJLY_APP_ID'),
'options' => [
'host' => 'ws-sa.mawjly.com',
'cluster' => 'sa',
'useTLS' => true,
],
],Laravel Echo (front-end)
// Before
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_PUSHER_APP_KEY,
cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
forceTLS: true,
});// After
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_MAWJLY_APP_KEY,
wsHost: 'ws-sa.mawjly.com',
cluster: 'sa',
forceTLS: true,
});Python — pusher
# Before
import pusher
p = pusher.Pusher(
app_id='APP_ID', key='KEY', secret='SECRET',
cluster='mt1', ssl=True,
)# After
import pusher
p = pusher.Pusher(
app_id='APP_ID', key='KEY', secret='SECRET',
host='ws-sa.mawjly.com', cluster='sa', ssl=True,
)Ruby — pusher
# Before
Pusher.app_id = 'APP_ID'
Pusher.key = 'KEY'
Pusher.secret = 'SECRET'
Pusher.cluster = 'mt1'
# After
Pusher.app_id = 'APP_ID'
Pusher.key = 'KEY'
Pusher.secret = 'SECRET'
Pusher.host = 'ws-sa.mawjly.com'
Pusher.cluster = 'sa'Node.js — pusher
// Before
const Pusher = require('pusher');
const pusher = new Pusher({
appId: 'APP_ID',
key: 'KEY',
secret: 'SECRET',
cluster: 'mt1',
useTLS: true,
});// After
const Pusher = require('pusher');
const pusher = new Pusher({
appId: 'APP_ID',
key: 'KEY',
secret: 'SECRET',
host: 'ws-sa.mawjly.com',
cluster: 'sa',
useTLS: true,
});Go — pusher-http-go
// Before
client := pusher.Client{
AppID: "APP_ID", Key: "KEY", Secret: "SECRET",
Cluster: "mt1", Secure: true,
}
// After
client := pusher.Client{
AppID: "APP_ID", Key: "KEY", Secret: "SECRET",
Host: "ws-sa.mawjly.com", Cluster: "sa", Secure: true,
}.NET — PusherServer
// Before
var options = new PusherOptions {
Cluster = "mt1",
Encrypted = true,
};
var pusher = new Pusher("APP_ID", "KEY", "SECRET", options);// After
var options = new PusherOptions {
HostName = "ws-sa.mawjly.com",
Cluster = "sa",
Encrypted = true,
};
var pusher = new Pusher("APP_ID", "KEY", "SECRET", options);Migrating webhooks
Pusher webhooks are configured in their dashboard. Mawjly webhooks are configured in your Mawjly app → Webhooks tab. The payload format is identical (Pusher V1 protocol), so your webhook receiver code does not change.
Note your Pusher webhook destinations
Open Pusher → App → Webhooks. Note the URL, the events subscribed (channel_occupied / channel_vacated / member_added / member_removed / client_event), and the signing secret if you verify signatures.
Re-create them in Mawjly
In the Mawjly dashboard, go to your app → Webhooks → “Add webhook”. Paste the same URL, select the same event types. Mawjly will issue a new webhook signing secret.
Verify signatures (if you do)
Mawjly webhooks are signed using HMAC-SHA256 of the raw body, with your app secret. The header is X-Pusher-Signature (Pusher-compatible name kept intentionally) so any signature-verification code that worked for Pusher continues to work — only the secret changes.
Test delivery
In the Mawjly dashboard, hit “Test fire” on your new webhook. You’ll see the delivery status, response body, and a one-click replay button if it failed.
Migrating encrypted channels
Encrypted (private-encrypted-*) channels are end-to-end encrypted with a master key you provide. Move the same master key to your Mawjly app:
- Mawjly dashboard → app → Keys tab → “Encryption master key” → paste your existing Pusher encryption master key.
- Your client-side code does not change —
pusher-jswill decrypt with the same key.
If you don’t have an existing key (you set up encrypted channels long ago and didn’t save it), you can generate a fresh one in the Mawjly dashboard. Existing encrypted channel subscribers will need to refresh their connection to receive new messages.
Migrating presence channels
Presence channels work identically. Your auth endpoint signs socket_id:channel_name:channel_data with HMAC-SHA256 of your app secret — same algorithm Pusher uses, same shape Mawjly verifies. No change needed beyond the four config values above.
If you’re hitting 403s after migration, use the Auth callback tester in Mawjly dashboard → app → Keys tab. It hits your auth endpoint with the exact signed-auth POST pusher-js would send and tells you what’s wrong (missing auth field, wrong key, bad signature, etc).
Pre-migration checklist
Before you flip the env vars in production, do this in order:
- Create your Mawjly app. Sign up, create app, save the credentials. Note the
app_id,key,secret, and (if you use them) the encryption master key. - Migrate webhooks (see above) so events you trigger after the swap reach the same destinations.
- Test in staging. Set the four config values in your staging environment and verify chat / notifications / dashboards work.
- Compare latency. Open the live debug console, send a few events, watch how fast they appear. From the GCC, expect sub-50ms median.
- Verify your auth endpoint. Use the Auth callback tester in the Mawjly dashboard to confirm presence/private channels will work.
Production cutover
The cleanest cutover is to do it in one deployment per environment — change all four config values together. There’s no need to run Pusher and Mawjly in parallel.
If you want extra safety, you can subscribe the same browser tab to both providers temporarily and compare event delivery — but in practice, since the protocol is identical, this is overkill. The change is functionally equivalent.
Rollback plan
If something unexpected happens, rollback is symmetric: revert the four config values to your Pusher credentials and redeploy. Your Pusher account is untouched throughout.
For this reason we recommend keeping your Pusher account active for at least 7 days after cutover. Cancel it once you’re confident.
Cost calculator
Use Mawjly’s interactive calculator to estimate your bill at your current message volume + concurrent connection levels. The calculator uses real plan caps and shows annual savings vs Pusher.
Migration credits
If you’re moving from Pusher and your last 3 months of Pusher invoices total >$200, email us at hello@mawjly.com with a copy. We’ll credit your Mawjly account up to one month of free service to cover the switching cost.
FAQ
Will my existing pusher-js / pusher-php-server / etc. version work?
Any version released after early 2018 supports the wsHost / host config option. If you’re on a really old version (2017 or earlier), upgrade your Pusher SDK first — it’s a backwards-compatible change.
Do I need to change my channel naming?
No. private-, presence-, and private-encrypted- channel-prefix conventions are part of the Pusher protocol and Mawjly honors all of them.
What happens to in-flight events at cutover?
When you redeploy with the new config, your clients reconnect to Mawjly. Events published to Pusher after that moment go nowhere on Mawjly’s side — and vice versa. So either:
- Drain your Pusher publishers first (let in-flight queues finish), then deploy.
- Or accept a few seconds of cross-provider gap (acceptable for chat / notifications, not for ordered streams).
Can I keep using my Pusher dashboard for analytics?
Pusher will only show analytics for events you continue sending to Pusher. After cutover, all your real-time analytics live in the Mawjly dashboard (per-app charts under the Analytics tab).
Does Mawjly support the same client SDKs Pusher does?
Yes — every officially-maintained Pusher SDK works unchanged: pusher-js, pusher-websocket-swift, pusher-websocket-java (Android), pusher-websocket-react-native, plus the server libraries listed above.
What about Pusher Beams (mobile push notifications)?
Mawjly v1 does not include a Beams equivalent. If you use Pusher Beams, keep that account active alongside Mawjly. We’ll evaluate adding a similar product based on customer demand — let us know.
What about Pusher Channels’ specific add-ons (channel rate limits, IP allowlists)?
These are configurable per-app in Mawjly’s Settings tab. Channel rate limits are part of the plan tier (see pricing).
Need help?
Migration is intentionally simple, but if you hit anything unexpected, email support@mawjly.com with the SDK + version you’re using and we’ll respond within one business day. Most migration questions are answered in under an hour.
For team migrations (multiple production apps, complex webhook fan-out), we offer a free 30-minute migration call — reply to your signup email or write to hello@mawjly.com.