Integration prompts
Prompt: Node backend
A prompt to paste into a coding agent that sends trusted events and reads customer state from a Node.js backend with a secret key.
Paste the prompt below into your coding agent from the root of your Node.js service. Put the
test secret key in the service's secret store first; the agent must read it from the
environment, never write it into code. If the frontend will read customer state, ask the
operator for a secret key that includes customer_sessions:write (not a default scope) and for
the publishable key; neither can be created with a secret key.
Integrate this Node.js backend with Orishare (loyalty and engagement API).
Read first, and use only what they define:
- https://docs.orishare.com/docs/agent-guide, especially the credential matrix at
https://docs.orishare.com/docs/agent-guide#credential-matrix and "Stable retry ids"
- https://docs.orishare.com/capabilities.json (supported operations and credential kinds)
- https://docs.orishare.com/openapi.json (the only source of routes and schemas)
Do not invent routes, fields or rule actions.
Credentials:
- Secret key from process.env.ORISHARE_SECRET_KEY (osk_test_... in a test environment). Server only. Never
send it to a browser, never log it, never commit it.
- Do not use a personal access token (opat_...) at runtime; it is for operators.
- If a browser needs Orishare, mint it a customer session (step 4); never give it this key.
Build:
1. Plain fetch with API = 'https://api.orishare.com' (the origin; paths start with /v1).
@orishare/api-client is not on npm (it lives in the Orishare repository); do not add it.
2. After each business moment commits in our database, send it with POST /v1/events:
{ id, name, customer_id, occurred_at, properties }. customer_id is OUR user id (the external
id), never an Orishare cust_ id. The id is derived from the business record (order_<id>,
refund_<id>) so a retry is a duplicate, not a second event; the event id is the only dedupe
(a secret-key POST /v1/events ignores Idempotency-Key). Event names are dotted snake_case
and an unknown name is accepted as a new event, so keep them in one constant list. Events to
send: <order.paid with total_cents and order_id; order.refunded with order_id>.
3. For bulk backfills use { events: [...] } (at most 100). The answer is 207 with one result per
item, matched by index (a rejected item has error and no id); resend only rejected items after
fixing them, with the same ids.
4. If the frontend needs customer reads, add an authenticated endpoint that resolves our signed-in
user to their internal Orishare id (cust_..., from POST /v1/customers or
GET /v1/customers?external_id=...) and calls POST /v1/customer_sessions with
{ grant_type: "secret_key", customer_id } and one Idempotency-Key per mint intent (reused on
its retries), returning only { customer_token }. If a retry is answered by replay
(customer_token: null), mint again with a new Idempotency-Key. This needs
customer_sessions:write on the key; a 403 insufficient_scope means the operator must issue
such a key.
5. Trusted reads: GET /v1/customers/{id}/state and GET /v1/customers/{id}/balances.
6. Async: a 202 is stored, not evaluated. When we need the outcome, poll GET /v1/events/{id} with
the evt_ id from the admission result until status is evaluated (our business id is its
client_event_id and is not accepted there), or use mode: "sync" only on low-volume paths (it
may still return 202 with fallback: true).
7. Webhooks: expose a POST endpoint and verify Orishare-Signature (t=<unix>,v1=<hex>) as
HMAC-SHA256 of `${t}.${rawBody}` with the endpoint secret, constant-time compare, reject t
older than 300 seconds; make the handler idempotent by Orishare-Event-Id.
8. Repeating requests, exactly as https://docs.orishare.com/docs/agent-guide#when-a-request-may-be-repeated:
after 429, or 409 with Retry-After, wait and resend unchanged. After a lost answer (timeout,
connection error, 5xx) resend only when the operation's retry class in capabilities.json is
atomic_replay or keyed_resume (rest_operations[].retry in capabilities.json), with the
same Idempotency-Key and body. POST /v1/events is outcome_unknown but deduplicated by event
id: resend the same event with the same id. Any other outcome_unknown write: read the
resource first. No blanket retry loops for writes; never resend after 409 event_id_conflict
or 422 idempotency_key_reused.
9. Hosted widget (if I ask for it): https://docs.orishare.com/docs/hosted-widget, backend path.
Reuse the session endpoint of step 4 (it returns only { customer_token }, minted fresh on each
call; 401 or null when nobody is signed in) and add to our page the script tag for
<widget origin>/v1/embed.js (the widget origin is in the dashboard snippet; ask me for it) plus
orishare.push(['mount', { portal: <ptl_...>, publishableKey: <opk_...>, getSession }]),
plus target: '#element' only when the portal's layout is inline (omit it for a launcher),
where getSession posts to that endpoint (same origin as the page) and returns its JSON. When
our user signs out, push ['signOut'], wait for the signed_out event, then end our own session;
push ['signIn'] after sign-in. The minted session is ours:
the widget never revokes it; it expires on its own (15 minutes by default). The secret key
never leaves this service; the publishable key in the snippet is fine.
Add tests with a stubbed fetch for the event sender (same id on retry, 207 handling).
Verify against the test environment and report changed files and anything unverified.