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.
A button here fires a first-party event. A journey turns it into a notification in the bell ↗ in the top nav — your feed, this session, no login.
- 1You fired a first-party event (source: inapp) carrying your anonymous id.
- 2The engine resolved your anon 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 back into the loop.
Unread in your bell: 0. Each item carries an actionUrl — clicking the row emits inapp.item_clicked, a real first-party event 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";
await sendFeedItem({
recipient: { anonymousId: user.id }, // 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,
});
},
});This is the journey that drops the notification into your bell. It reads your name off the event and personalizes the title — same anonymous identity end to end, zero identify call.
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, Banner, Toast — with props, headless escape hatches, and granular imports.
Theming & customization
Style the @hogsend/react components with CSS variables, classNames, data-* state attributes, asChild, and render props — no Tailwind or CVA dependency.