Keep Portal access in its original audience
Exchange an issued one-time bootstrap capability through exchange-access, then use the returned Portal continuation through the guestPortalToken security scheme. A subject, profile, history row, proof UUID or grant UUID does not itself authorize native management.
Keep the owning flow on its actual authorized origin. Verified hosted domains can serve /guest, /guest/notifications and /guest/notifications/settings only within their current strict host/publication admission. A Portal session cannot widen the original reservation, waitlist, preorder or QR presentation audience. The isolated widget frame remains /widgets/:connectionId; do not bridge a gpc bearer into another tab or origin through a URL.
Keep OTP, contact-proof inputs, Portal/native capabilities and credential-bearing responses outside Redux, query caches, public URLs, logs and analytics. Remove a consumed bootstrap fragment from the owning page; never replace it with the continuation token. Sign-out revokes the Portal session.
Initial entry can start from a current owned reservation/waitlist manage capability through reservations.preparePortalEntry, or a preorder READ SESSION through public-preorders.preparePortal. Save the original idempotency key before sending. Both commands return only a private reentryContext hint and fixed expiry. Request a real code through request-reentry and confirm it through confirm-reentry before adopting the returned Portal continuation. The server selects the existing contact; no booking ID, hint, request receipt or candidate contact grants Portal access.
For bootstrap entry, privately retain the original gpb2 token and a stable exchangeId UUID before sending exchange-access. After an uncertain response or reload, explicitly repeat that same pair. Recovery returns the original continuation and expiry only while its original subject, session and token remain current; it never rotates or extends access. Omitting exchangeId keeps one-shot semantics. Clear private pending input on expiry, definite denial, sign-out or identity replacement.
First successful workflow
Follow the exact generated method contract at each step and keep private recovery context within its original audience.
- Use current native management to prepare a private contact hint, retain the original code-request UUID, then explicitly request and confirm the actual delivered code. Alternatively exchange an issued bootstrap with its retained exchangeId. After a confirmed continuation, read the current profile, loyalty and consent preferences.
- Read cursor-bounded history and exact native details with current contact assurance.
- For native management, complete a fresh target proof and explicitly issue the original command; inspect receipt separately from current nullable access.
- For QR history, show the native statement and explicitly associate only the original membership, then read its own receipts.
- Update editable profile/marketing fields with their expected revision, use separate privacy requests where needed and revoke the Portal session on sign-out.
Read bounded history without acquiring management
history returns RESERVATION, WAITLIST, PREORDER, QR_VISIT, VISIT and ORDER summaries. Continue only with the returned cursor and unchanged guest/venue scope; stop at the end. History, native details and receipts require their current server-authorized contact assurance. A logged-in profile or old verification timestamp is not fresh contact proof.
reservation-details, waitlist-details and preorder-details expose only the exact guest-owned read projection. hasVerifiedSource does not grant a bearer. A preorder native projection may be null; reservation originalGuarantee and currentGuarantee are separate retained/current facts. Ordinary management expiry does not erase eligible history, but history never renews a native grant.
payment-receipts returns at most 25 eligible original consumed-consent receipts per page. Its opaque PAYMENT_RECEIPTS cursor belongs to that history policy and cannot be exchanged with a general history cursor. Preserve the original acknowledgement separately from current funds and application progress. Reading a receipt neither charges a payment nor grants management, payment access or the whole session bill.
Complete a fresh server-selected native entry proof
Choose one exact targetKind RESERVATION, WAITLIST or PREORDER and its targetId. The native owner authorizes the retained source and actual browser Origin before proof preparation. Explicit contactType, contact and displayName must match that source; neither the client nor a history row selects a verification reference.
prepare-native-entry-proof returns a server-selected proofId and PREPARED acknowledgement. Supply the actual delivered six-digit code to verify-native-entry-proof. A wrong code consumes the canonical attempt budget before its HTTP 400 response. Retry an uncertain response with the same key and code; entering a different code is a new explicit verification attempt with a new key. Never submit a placeholder OTP to discover status.
native-entry-proof-from-reentry accepts an actual consumed reentryChallengeId for this exact current Portal session/contact version. That UUID is not a bearer and cannot be inferred from a recent session timestamp. The server binds it to this target proof.
After reload or a lost acknowledgement, read native-entry-proof once on explicit recovery using the saved target and proofId. PREPARED, VERIFIED and CONSUMED describe retained proof lifecycle only. This read sends no OTP, renews no deadline and issues no management capability; do not poll it or manufacture verification.
- Save the exact target, preparation key and input in the existing owning private recovery context before submission.
- Explicitly prepare contact proof, or bind an actual consumed current-session reentry challenge.
- For PREPARED, ask the guest for the delivered code and submit one explicit verification command.
- On uncertainty, preserve the original key/input and recover the saved proof; do not prepare a replacement.
- After genuine verification, explicitly issue native entry with the exact proofId and its own retained command key.
Separate the original issuance receipt from current access
issue-native-entry atomically consumes the purpose-bound proof through the native owner. Its response separates receipt, the immutable nonsecret original acknowledgement, from current, the independently evaluated continuation. Receipt proofId/grantId and issuedAt/expiresAt remain original through replay.
current.state is AVAILABLE, SUPERSEDED, EXPIRED, REPROOF_REQUIRED or UNAVAILABLE. current.manageToken and effectiveExpiresAt are nullable. Render the original acknowledgement even when current access is unavailable; never treat a receipt as a bearer or issue another grant to recover a lost response.
Persist the original issue key, target and proofId before sending. After a lost response or reload, explicitly replay that same key/input in the original authorized audience. A different input with the same key fails; replay does not extend expiry or resurrect revoked authority. Use a returned current native capability only in its owning reservation/waitlist/preorder flow.
import { createGuestClient } from '@hallify/sdk/browser';
const guest = createGuestClient();
// Save this nonsecret command in the owning private recovery journal before send.
export function retainNativeEntryCommand({ targetKind, targetId, proofId, key }) {
const args = Object.freeze({
targetKind,
targetId,
'idempotency-key': key,
issueGuestPortalNativeEntryDto: Object.freeze({ proofId }),
});
return Object.freeze({
args,
send: (guestPortalToken) =>
guest['guest-portal'].issueNativeEntry(args, {
auth: { guestPortalToken },
}),
});
}
// On an uncertain response, explicitly call the saved command again.
// Inspect data.receipt independently from data.current and its nullable manageToken.Associate the original QR membership with explicit history consent
Read portal-association-statement with the original live sessionId, tableGuestToken and actual Origin. Render the exact returned locale, termsVersion and termsDigest. Preparation freezes that membership/session/seat, subject, commandId, expectedRevision and statement; first association uses expectedRevision 0.
prepare-portal-booking-proof and associate-portal-history support either tableGuestToken alone or tableGuestToken together with guestPortalToken. Table-only BOOKING verification reserves the exact future Portal session and uses a server-selected isolated real OTP challenge. The guest explicitly supplies the actual code when accepting; wrong-code attempts consume budget. Returned verificationId/proofId are server-selected references, never capabilities; verificationId is null for reentry.
prepare-portal-reentry-proof requires BOTH tableGuestToken AND guestPortalToken, plus an actual consumed PORTAL_REENTRY challenge for that current Portal session/contact version. A Portal token alone, a table token alone or a recent login timestamp cannot satisfy this dedicated reentry operation.
associate-portal-history consumes the exact prepared proof and first ACCEPT atomically. Keep outcome, associationId and original decision separately from nullable continuation. A first verified table-only association may return the reserved Portal continuation; an existing-session call returns null. Recover a lost response with the same original key/body and original QR capability/Origin, without repeating preparation or choosing another seat.
import { createGuestClient } from '@hallify/sdk/browser';
const guest = createGuestClient();
// originalBody contains the server statement and a real consumed reentry challenge.
export function retainQrReentryPreparation({ sessionId, originalKey, originalBody }) {
const args = structuredClone({
sessionId,
'idempotency-key': originalKey,
prepareGuestPortalQrReentryProofDto: originalBody,
});
return ({ tableGuestToken, guestPortalToken }) =>
guest['table-access'].preparePortalReentryProof(args, {
auth: { tableGuestToken, guestPortalToken },
});
}Keep own QR receipts bound to their original membership
QR_VISIT uses associationId as its history ID. qr-visit, qr-own-order-requests and qr-own-order-request resolve only the immutable original membership, session and seat. An order transfer, table merge or later seating does not move receipt ownership to another guest or expose SESSION_BILL. Missing/corrupt frozen witnesses produce unavailable data rather than a reconstruction from current CRM or Orders.
qr-consent may be inspected and withdrawn by the current Portal owner without a new OTP or live membership. WITHDRAW is append-only at expectedRevision; replay returns that original decision even after later reconsent. It does not erase another immutable receipt or create table authority.
For reconsent, obtain the current server statement/revision, prepare a fresh purpose-bound BOOKING or consumed-reentry proof, then explicitly accept-qr-history-consent. Reconsent remains attached to the original association and requires no live table membership. It cannot overcome hardRevoked or restore a native table/payment grant. Private history additionally requires current contact assurance, readable proof and latest ACCEPT.
- Read an owned original QR visit
- Read current QR history consent and statement
- List own original QR order receipts
- Read one own frozen QR order receipt
- Prepare fresh QR reconsent contact proof
- Prepare QR reconsent from real current reentry
- Append fresh verified QR history consent
- Withdraw own QR history consent
Use generated browser methods and a private recovery command
The browser SDK groups Portal methods under guest['guest-portal'] and initial native QR association under guest['table-access']. Pass credentials in per-call auth keyed by guestPortalToken/tableGuestToken, exactly as that operation declares. Generated args deliberately contain no duplicate auth or Origin headers; the browser supplies its actual Origin. The Node partner client and its server keys are a separate surface, not Portal guest capabilities.
reentry-context is a PRIVATE COMMAND (POST), never profile-cached metadata. It returns a nullable private reentryContext hint/expiry for the canonical contact-recovery flow, which may still need genuine fresh OTP verification. It is neither a verified contact nor a Portal/native bearer. Do not add the response to an RTK query, Redux, URL or cross-tab credential bridge. Continue through the existing canonical contact-recovery flow.
import { createGuestClient } from '@hallify/sdk/browser';
const guest = createGuestClient();
export async function requestPrivateRecoveryTarget(guestPortalToken) {
const { data } = await guest['guest-portal'].reentryContext(undefined, {
auth: { guestPortalToken },
});
// Keep this nullable private target in the owning recovery flow only.
// It is neither verified contact nor a Portal/native bearer.
return data.reentryContext;
}Recover explicit original commands without polling
Keep the original operation, source audience, target, command/proof IDs, idempotency key and exact input in the existing owning private recovery context before an idempotent native-entry or QR command. Preserve OTP/token privacy; the SDK provides no credential store. On UNKNOWN, lost responses or reload, explicit recovery uses that original identity. A read or a newer profile does not prove the result of an uncertain command.
SdkError.apiCode exposes the stable API code; SdkError.outcome reports request outcome, including uncertainty after a sent mutation. The SDK performs no automatic retry or key allocation. Changing an OTP is an explicit new verification attempt, not an exact retry of the old command; it consumes the remaining canonical budget. Respect expiry, source revocation, current contact assurance and native denial without bypassing them.
Profile, loyalty, marketing/consent decisions and privacy requests retain their existing separate contracts. Code examples define functions around caller-supplied synthetic context and send no command on page load. They do not establish browser, database, HTTP or provider qualification.