Lambda + embed
Visitor API, origin allowlist, reply fan-in, and the floating chat UI on your domain.
Showcase
A portable pattern for visitor chat: your Lambda and embed on the edge, PingStack for the team inbox. Built on the same App Platform path as other conversation bridges — not a first-party App Store install.
Visitor API, origin allowlist, reply fan-in, and the floating chat UI on your domain.
Custom app, inbound webhook URL, signing secret, per-visitor discussions, and “Reply via Web Chat” for members.
Preferred third-party outbound path: PingStack POSTs replies to your/delivery with X-PingStack-Signature.
In PingStack
Labels match the current Add Custom App wizard. Use your PingStack environment (e.g. app.dev.pingstack.app). You need Server Owner or Admin.
Server Owner/Admin → Apps → + Add Custom App → Conversation bridge. Provider: My own endpoint. Channel kind: Website chat. Outbound: Signed delivery URL pointing at your Lambda /delivery. Bind (or create) a channel such as www-chat. Copy the inbound Webhook URL and Signing secret — the secret is shown once.
Run examples/pingstack-web-chat/lambda/deploy.sh (Function URL + DynamoDB sessions). Set PINGSTACK_ENDPOINT_URL, PINGSTACK_SIGNING_SECRET, and ALLOWED_ORIGINS after the PingStack app exists. Browser calls hit /session/*; PingStack POSTs signed replies to /delivery.
Put the browser API on your domain (e.g. chat.azimuthsystems.net → CloudFront → Lambda). Set PUBLIC_PINGSTACK_CHAT_API_BASE + PUBLIC_TURNSTILE_SITE_KEY and rebuild. PingStack secrets never ship to the client; delivery stays on the Function URL.
Important: choose Conversation bridge → My own endpoint →Website chat. The hosted SMS provider path (Twilio) is a different channel kind. Do not look for a first-party “Web Agent” App Store install for this pattern.
Example code
Third-party HMAC apps open a conversation with a root message, then asession.ensure post that includes parentSeq (App Conversations §10.3). The sample handler does this for you on the visitor’s first send.
// POST /session/{id}/send — first message opens a web: session
const root = await postIngress(JSON.stringify({
body: `${visitorName} started a web chat`,
username: visitorName,
}));
await postIngress(JSON.stringify({
body: text,
username: visitorName,
session: {
externalId: visitorId, // becomes web:{id}
label: `Web: ${visitorName}`,
parentSeq: root.seq, // required to create (§10.3)
ensure: true,
},
}));Full handler:/showcase/web-chat/lambda/index.mjs(also under examples/pingstack-web-chat/ in the repo).
Embed
Point data-api-base at your domain’s chat API (not*.pingstack.app). Include the Turnstile site key. Swap the scriptsrc for a self-hosted copy if you prefer.
<script
src="/chat/widget.js"
data-api-base="https://chat.yourdomain.example"
data-turnstile-site-key="0x4AAAA…"
data-theme-color="#ff9026"
data-position="bottom-right"
></script>Deploy script wires DynamoDB sessions, origin allowlisting, and Cloudflare Turnstile on/session/start (required name + captcha). In-memory mode remains available when SESSION_TABLE is unset (local demos only).
PingStack also has a hosted widget/gateway path (poll queue, CDN loader). This showcase is the framework story — same conversation primitives, your compute — so you can ship on any site without waiting on a first-party install.