Try it live
Fire a first-party event from this page and watch a journey drop a notification into the bell in the top nav — same anonymous identity, no login.
This is the same @hogsend/js + @hogsend/react you just installed, running on these docs. You are an anonymous visitor — no login, no identify call. The bell in the top-right is your feed. Fire an event below and watch a journey reach it.
Fire it. Watch your bell.
Sign up below, then fire a real lifecycle event. A journey turns it into a notification in the bell ↗ in the top nav — your feed stays anonymous, this session.
Sign up above to fire real lifecycle messages.
- 1You fired a first-party event (source: inapp) keyed to your id.
- 2The engine resolved your id to a canonical contact key and routed it.
- 3A journey triggered on that event, read your name, and called sendFeedItem.
- 4It landed in your bell ↗ — open it. Clicking the item fires inapp.item_clicked (and a link.clicked on its tracked CTA) back into the loop.
Unread in your bell: 0. Each item carries a tracked CTA — clicking the row emits inapp.item_clicked and a link.clicked, both real first-party events a journey can react to.
import { days } from "@hogsend/core";
import { defineJourney, sendFeedItem } from "@hogsend/engine";
import { DemoEvents } from "./constants/index.js";
export const demoWelcome = defineJourney({
meta: {
id: "demo-welcome",
name: "Demo — In-app welcome",
enabled: true,
trigger: { event: DemoEvents.WELCOME }, // "demo.welcome"
entryLimit: "unlimited", // re-fire freely
suppress: days(0),
},
run: async (user) => {
const n = user.properties.name;
const name = typeof n === "string" && n ? n : "there";
const greeting = name === "there" ? "Welcome" : `Welcome, ${name}`;
await sendFeedItem({
recipient: { anonymousId: user.id }, // your canonical key
type: "welcome",
// Report the event — the site banner already greets by name.
title: "Your welcome journey just ran ✅",
body: `${greeting} — you fired demo.welcome, a journey ran, and it dropped into your bell.`,
actionUrl: "https://hogsend.com/docs/client-side/try",
journeyStateId: user.stateId,
});
},
});This is the journey that drops the notification into your bell. It reads your name off the event and personalizes the title — your in-app feed stays keyed to one id end to end, so the bell works whether or not you signed up.
What just happened
Nothing on this page is faked. The button called client.capture("demo.welcome", { name }). That POSTed a first-party event (source: "inapp") carrying your anonymous id. The engine resolved it to a canonical contact key, a journey triggered on demo.welcome, read your name off the event, and called sendFeedItem({ recipient: { anonymousId: <your key> } }) — which published to the exact feed the bell polls.
The round-trip uses one identity the whole way. The browser never identified itself: a pk_ publishable key has no user token, so client.getDistinctId() stays the anonymous id and userId stays null. Personalization rides as an event property, not an identify call — the journey reads user.properties.name and writes it into the notification title.
The item carries an actionUrl, so clicking the row emits inapp.item_clicked — another first-party event, one a journey can trigger on. The loop closes on itself.
// the demo journey, in full — apps/api/src/journeys/demo-inapp.ts
export const demoWelcome = defineJourney({
meta: {
id: "demo-welcome",
trigger: { event: DemoEvents.WELCOME }, // "demo.welcome"
entryLimit: "unlimited", // re-fire the demo freely
suppress: days(0),
},
run: async (user) => {
const n = user.properties.name;
const name = typeof n === "string" && n ? n : "there";
await sendFeedItem({
recipient: { anonymousId: user.id }, // re-resolves to your canonical key
type: "welcome",
title: `Welcome, ${name} 👋`,
body: "You fired an event. A journey ran. This is the result.",
actionUrl: "https://hogsend.com/docs/client-side/try",
journeyStateId: user.stateId,
});
},
});Components
Pre-built React UI for in-app surfaces — NotificationBell, FeedPopover, NotificationFeed, SurveyBlockView, Banner, Toast, PreferenceCenter — with props, headless escape hatches, and granular imports.
In-app surveys
Fire an event from this page, answer the NPS card it drops in your feed, and watch a second journey read your score back off the spine — one identity, no login.