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.
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
| Scope | Why |
|---|---|
identity:read | optional 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).
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.
import { verifyWebhookSignature } from "@pambase/sdk";app.post("/pambase/webhook", async (req, res) => {const raw = req.rawBody; // the EXACT bytes PAMbase signed — verify before JSON.parseconst ok = await verifyWebhookSignature({secret: process.env.PAMBASE_WEBHOOK_SECRET!,signature: req.headers["x-pambase-signature"], // HMAC-SHA256(secret, raw) hexpayload: 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 appconst 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});
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.
// Same endpoint, later — the timer you set fires back:if (event.type === "schedule.fired") {await notifyUser(event.payload.note); // "Order lunch?"}
Next
- Calendar — the producer whose slots you consume.
- Webhooks & scheduling — signature, retries, and timers in depth.
- Catalog — the
schedule.*payload shapes you branch on.