← Use cases

Live auctions, scores and big audiences

Push the current bid or score to thousands of viewers at once, give newcomers the latest state instantly, and show a real “watching now” count.

Live auctions, sports scores, quiz shows, flash sales and live-stream comments have one thing in common: thousands of people watch the same thing at the same moment, and a second of delay changes the outcome. A bid that arrives late or a score that lags the broadcast sends people elsewhere.

What your users get

  • The current bid, score or stock level the instant it changes.
  • "12,408 watching now", a number that is real and moves.
  • The latest state the moment they open the page, even mid-event.

Built with

  • Cache channels (cache-), so every newcomer gets the current price or score immediately, without hitting your database.
  • Large presence channels (presence-large-), with up to 100,000 members, a live count and a sample of who is there.
  • Subscription counts (pusher:subscription_count) for a simple viewer count on public channels.
  • Free fan-out: one bid published to 20,000 viewers counts as one message, not 20,000.

Step 1: publish each change once

Your server checks the bid or records the score, then publishes the new state to everyone:

app.post('/auctions/:id/bids', requireLogin, async (req, res) => {
  const bid = await db.auctions.placeBid(req.params.id, req.user.id, req.body.amount); // rejects lower bids
  await pusher.trigger(`cache-auction-${req.params.id}`, 'bid', {
    amount: bid.amount,
    bidder: bid.displayName,
    endsAt: bid.endsAt,
  });
  res.json(bid);
});

Always send the full state (the current amount and end time), not a difference: a viewer who missed an event is correct again with the next one.

Step 2: show it to the audience

const auction = pusher.subscribe(`cache-auction-${id}`);
auction.bind('bid', ({ amount, bidder, endsAt }) => {
  price.set(amount);
  feed.prepend(`${bidder} bid ${amount}`);
  countdown.until(endsAt);
});

The cached last bid arrives right after subscribing, so the price is never blank.

Step 3: a viewer count

For a count only, turn on Send subscriber counts in the app settings and bind to the count event on the same public channel:

auction.bind('pusher:subscription_count', ({ subscription_count }) => watching.set(subscription_count));

Without writing code, the one-line embed script does the same: <span data-hibira-online="cache-auction-42">0</span> watching.

To show who is there too, use a large presence channel and @hibira-io/large-presence:

import { LargePresence } from '@hibira-io/large-presence';

const room = LargePresence.subscribe(pusher, `presence-large-auction-${id}`, { maxMembers: 50 });
room.on('ready', ({ count }) => audience.render(count, room.members));
room.on('change', ({ count }) => audience.render(count, room.members));

The count stays exact while members' joins and leaves arrive as one small update per second, instead of one event per person.

Tips

  • Validate every bid, answer or purchase on your server. Never let clients publish the state of the event themselves.
  • Plan your connections, not your messages: 20,000 viewers are 20,000 concurrent connections, and the price depends on the peak. See pricing.
  • For a sell-out moment such as a ticket release, publish stock levels every second or two rather than on every sale.
  • Test with the real audience size before the big day: open many connections from a load-test tool and watch the app page in the portal.