Migrate from Pusher to Vask (Rails). Drop-in for pusher-http-ruby.

Vask speaks the Pusher protocol, so a Rails app using pusher-http-ruby and pusher-js usually migrates by changing credentials, host, and cached assets.

Migration summary

At a glance

low risk
Type
Drop-in host swap
Typical time
10-15 minutes
Server SDK
pusher-http-ruby unchanged
Client SDK
pusher-js unchanged
Auth changes
reuse /pusher/auth; keep Rails CSRF header
Rollback
restore Pusher credentials and rebuild cached assets

Files touched

  • config/initializers/pusher.rb
  • Rails credentials or ENV
  • app/javascript/pusher.js
  • app/controllers/pusher_controller.rb

Get started

Realtime made simple.

Free Tier: 500K broadcasts/mo and 100 concurrent across unlimited apps. $10/mo when you outgrow it.

Minimal config diff

Set the Vask app key as both PUSHER_APP_ID and PUSHER_APP_KEY. If your SDK asks for an app id, use the Vask app key. Vask does not issue a separate customer-facing app id.

- PUSHER_APP_ID=123456
- PUSHER_APP_KEY=abc123
- PUSHER_APP_SECRET=xyz789
- PUSHER_APP_CLUSTER=us2
+ PUSHER_APP_ID=your_vask_key
+ PUSHER_APP_KEY=your_vask_key
+ PUSHER_APP_SECRET=your_vask_secret
+ PUSHER_HOST=wss.vask.dev
+ PUSHER_PORT=443
+ PUSHER_SCHEME=https
+ PUSHER_APP_CLUSTER=mt1

Then make sure the Rails initializer uses the explicit host:

require 'pusher'

Pusher.app_id = ENV['PUSHER_APP_ID']
Pusher.key = ENV['PUSHER_APP_KEY']
Pusher.secret = ENV['PUSHER_APP_SECRET']
Pusher.host = ENV.fetch('PUSHER_HOST', 'wss.vask.dev')
Pusher.port = ENV.fetch('PUSHER_PORT', 443).to_i
Pusher.scheme = ENV.fetch('PUSHER_SCHEME', 'https')
Pusher.cluster = ENV.fetch('PUSHER_APP_CLUSTER', 'mt1')
Pusher.encrypted = true

Rollback

Put the old Pusher values back in credentials or ENV, remove or override PUSHER_HOST, clear asset caches with your normal Rails workflow, and redeploy. Because the gem and client SDK stay the same, rollback is another credential swap.

What changes / what stays

  • Changes: host, key, secret, optional compatibility cluster, and any compiled JavaScript that embeds those values.
  • Stays: Pusher.trigger, channel names, event names, pusher-js, private/presence auth signatures, and any ActionCable adapter that reads the same Pusher config.

Server publish

Keep your existing publish calls:

Pusher.trigger('orders', 'order.created', { id: order.id })

If you publish through an ActionCable Pusher adapter, update the adapter credentials instead of adding a second publisher. If you use ActionCable with the default Redis adapter and no Pusher-compatible SDK, this guide is not a required migration; that stack can keep running as-is.

Client subscribe

Wherever Rails loads pusher-js (importmap, jsbundling-rails, Sprockets, or CDN), point the browser at Vask:

import Pusher from 'pusher-js';

const pusher = new Pusher(window.VASK_PUSHER.key, {
    cluster: window.VASK_PUSHER.cluster || 'mt1',
    wsHost: window.VASK_PUSHER.host || 'wss.vask.dev',
    wsPort: 443,
    wssPort: 443,
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
    authEndpoint: '/pusher/auth',
    auth: {
        headers: {
            'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')
                ?.content,
        },
    },
});

Do not assume process.env exists in the browser unless your Rails bundler replaces it. Template the public key and host into the page or expose them through your bundler's public-env pattern.

Private and presence channels

The auth route stays the same:

post '/pusher/auth', to: 'pusher#auth'
class PusherController < ApplicationController
  def auth
    return head :forbidden unless current_user

    render json: Pusher.authenticate(
      params[:channel_name],
      params[:socket_id],
      user_id: current_user.id.to_s,
      user_info: { name: current_user.name }
    )
  end
end

Rails 403s usually mean the CSRF header is missing, the session did not populate current_user, or the secret still contains the old Pusher value.

Verify

  1. Deploy or restart Rails with the new credentials.
  2. Hard refresh the browser and confirm a WebSocket connects to wss.vask.dev.
  3. Trigger a Pusher.trigger call from a controller, job, or bin/rails console.
  4. Subscribe to one public channel and one private or presence channel before cutting production traffic.

Gotchas

  • Asset cache still has the old host. Clear tmp/cache, expire CDN assets if needed, and run your normal asset pipeline so the browser receives the new wsHost.
  • Cluster expectations. Vask routes by PUSHER_HOST, not cluster. Keep mt1 only for SDK compatibility.
  • ActionCable paths can coexist. Leaving mount ActionCable.server => '/cable' in place is fine while pusher-js connects to Vask. Remove it later only if no clients use it.

Where to go next

Does pusher-http-ruby work unchanged?
Yes. Keep the gem and your existing trigger calls. Set the host to wss.vask.dev, use your Vask app key and secret. If your SDK asks for an app id, use the Vask app key.
What happens to private and presence auth?
Keep the same /pusher/auth route and Pusher.authenticate call. The Rails-specific check is the CSRF header from pusher-js; if private channels return 403, confirm X-CSRF-Token, current_user, and the Vask secret.
Do I need to replace ActionCable?
No. ActionCable on Redis can stay if it serves the app. This guide is for Rails apps already using pusher-http-ruby directly, or an ActionCable adapter that forwards through Pusher-compatible credentials.
How do I roll back if browsers still connect to Pusher or fail to connect?
Restore the old Pusher credentials, clear Rails asset caches, rebuild with your normal asset pipeline, and redeploy. The code path is symmetric because the migration is a host and credential swap.

Get going

Vask keeps the Rails migration at the credential layer: same gem, same client, same auth route, new host.

Make the switch

Drop in your Vask credentials. Keep your gem.

pusher-http-ruby on the server, pusher-js on the client, the same auth callback at /pusher/auth. Most teams ship the cutover in around fifteen minutes.