PAMbaseDocs
Guide

Commerce.

What you're building

A food / commerce app that reacts to the user's schedule — the consumer in PAMbase's canonical proactive flow. When a calendar app publishes a free slot, you get notified, set a timer, and offer lunch in time. The payoff: proactive commerce with no integration between the two apps.

Prerequisites

You have a connection token from the connect flow with these scopes (one permission each): identity:read and memory:read:schedule.*. You also have a registered webhook (an HTTP endpoint PAMbase calls when an event occurs) and its signing secret. See webhooks.

Scopes to request

ScopeWhy
identity:readoptional display name for prompts
memory:read:schedule.*receive the schedule signals other apps wrote

What to write (optional)

Record a confirmed order so future sessions adapt. remember() persists a durable memory (a lasting fact about the user).

typescript
await pambase.remember({
content: "Usual lunch order: ramen, no egg.",
kind: "preference",
scope: "food",
});
// → { accepted: true, memoryIds: ["mem_…"] }

How to read — subscribe to a webhook

Register your webhook URL in the dev portal, then handle inbound deliveries. PAMbase fires a signal.created webhook whenever a connected app emits a signal you subscribe to. Verify the signature over the RAW request body before parsing, dedupe, branch on the signal type, then schedule() a reminder.

webhook.ts
import { verifyWebhookSignature } from "@pambase/sdk";
app.post("/pambase/webhook", async (req, res) => {
const raw = req.rawBody; // the EXACT bytes PAMbase signed — verify before JSON.parse
const ok = await verifyWebhookSignature({
secret: process.env.PAMBASE_WEBHOOK_SECRET!,
signature: req.headers["x-pambase-signature"], // HMAC-SHA256(secret, raw) hex
payload: raw,
});
if (!ok) return res.status(401).end();
// Delivery is at-least-once → dedupe on the idempotency id before acting.
const deliveryId = req.headers["x-pambase-delivery"];
if (await seen(deliveryId)) return res.status(200).end();
await markSeen(deliveryId);
const event = JSON.parse(raw);
// event → { type: "signal.created", signalType, payload, aiId, connectionId, sourceApp, occurredAt }
if (event.type === "signal.created" && event.signalType === "schedule.slot_free") {
const { start } = event.payload; // { start, mins } from the calendar app
const fireAt = new Date(new Date(start).getTime() - 30 * 60_000).toISOString();
const pambase = appFor(event.aiId);
await pambase.schedule({ fireAt, kind: "reminder", payload: { note: "Order lunch?" } });
// schedule() → { id: "sch_…", fireAt }
}
res.status(200).end(); // ACK within ~5s, or the delivery is retried
});
Ack within ~5s

PAMbase expects a 2xx within roughly 5 seconds; a slow or failed ack triggers retries (backoff, up to 6 attempts). Do any slow work after acking, never before. Combined with the dedupe on X-PAMbase-Delivery, your handler stays correct under at-least-once delivery.

Why this is proactive

Proactivity is this recipe. The schedule() call above sets a future timer; when it fires, PAMbase delivers a schedule.fired webhook back to the same endpoint at reminder time — that's your cue to prompt the user.

fired.ts
// Same endpoint, later — the timer you set fires back:
if (event.type === "schedule.fired") {
await notifyUser(event.payload.note); // "Order lunch?"
}

Next