# Invite a guest to save a card

> Kismet-delivered invitations, partner-site guest accounts, and signed card-save completion events.


**Preview access required.** Card invitations are available only to explicitly enabled installations. Rehearse the complete flow in TEST before requesting LIVE access.

Use a card invitation to bring an existing verified guest back to their account on your website, open Kismet's hosted card form, and receive a signed completion event. Saving a card does not make a payment, enroll a membership, or grant marketing consent. A saved card or completion event never authorizes a charge.

## Prepare access

Use a server credential with `guest_wallet_requests.write` and `guest_auth.write`. Keep it in your server secret store. An authorized collection administrator or developer must register the exact callback URL as well as the partner origin. A registered origin alone does not authorize an authentication callback.

For events, separately grant `events.read`, `webhooks.read`, `webhooks.write`, and `guest_wallet_requests.events.read`. Add `payment_methods.events.read` only if you also need new-payment-method notifications. See [event destinations and signature verification](https://developers.kismet.travel/guides/developer-events.md).

Kismet derives the collection, application, installation and TEST/LIVE environment from the credential. There are no request-body overrides. The recipient must be an existing, verified guest visible to the collection. TEST additionally requires an explicitly registered test identity and always uses Stripe TEST mode; no LIVE wallet row is written.

## Create the invitation on your server

```ts
import { createKismetGuestAuthServerClient } from '@kismet-tech/sdk/server';

const guest = createKismetGuestAuthServerClient({
  serverKey: process.env.KISMET_SERVER_KEY!,
  baseUrl: 'https://api.ksmt.app/v1',
  origin: 'https://stays.example',
});

const invitation = await guest.createWalletRequest({
  guestProfileId: verifiedGuestProfileId,
  returnUrl: 'https://stays.example/account',
  delivery: 'kismet_email',
  reference: 'account-card-123',
}, { idempotencyKey: 'account-card-123' });
```

The REST equivalent is `POST /v1/developer/guest/wallet/requests` with the same JSON body and an `Idempotency-Key` header. Repeating the same key and input returns the original request without sending another email. Reusing the key with different input fails.

`kismet_email` sends a one-use sign-in proof directly to the guest's verified email. Your server receives status, never the proof or credential-bearing email URL. `deliveryState: sent` means accepted for delivery, not that the guest opened it. The proof expires after 15 minutes; the request expires after 24 hours.

Use `delivery: 'partner_link'` when your application will distribute the navigation link. Its `startUrl` carries a request ID only. Signed-out guests must independently authenticate. Do not describe that link as a magic login link.

`getWalletRequest(id)` reads status. `revokeWalletRequest(id)` stops unfinished requests. `resendWalletRequest(id)` sends a fresh Kismet email and invalidates the older proof and login grant. Resends are limited to five total sends, at least 60 seconds apart. An actual completed provider save remains recorded even if the request expired or was revoked afterward.

## Return to the partner account

The Kismet invitation page verifies proof after an explicit user action and keeps its short-lived login grant in a Kismet-origin HttpOnly cookie. It sends the browser to the registered partner return page with `kismet_wallet_request` in the fragment. Clear that fragment before application analytics run and retain only the non-secret request context in your UI.

For a Kismet-delivered invitation, configure the existing SDK handlers:

```ts
import { createKismetGuestAuthNextHandlers } from '@kismet-tech/sdk/next';

const handlers = createKismetGuestAuthNextHandlers({
  client: guest,
  sessionSecret: process.env.GUEST_SESSION_SECRET!,
  allowedOrigins: ['https://stays.example'],
  walletReturnPath: '/account/card-return',
});
// Mount POST handlers behind your same-origin account API:
// handlers.walletAuthorizationStart: accepts { requestId }
// handlers.walletAuthorizationComplete: accepts { code, state }
// handlers.walletCapture: accepts { walletRequestId }
```

The browser auth client (`createKismetGuestAuthBrowserClient`) provides `startWalletAuthorization(requestId)` and `completeWalletAuthorization({ code, state })`, including CSRF handling. Mount its `/wallet/start` and `/wallet/complete` paths beneath the configured auth base path; use the existing SDK CSRF endpoint and proof header for direct POSTs. `walletAuthorizationStart` adopts the existing `_kid_sid`, creates state and S256 PKCE, and returns an `authorizationUrl` on `kismet.travel`. Navigate there. Kismet uses its private invitation cookie to return a single-use code to the exact registered `walletReturnPath`.

On the return page, read `kismet_wallet_code` and `state` from the fragment, clear the URL, and POST them as `{ code, state }` to `walletAuthorizationComplete`. The SDK checks state, browser identity and expiration, redeems the code from the server, and stores the resulting guest session in an encrypted HttpOnly cookie. Session tokens never enter the browser response. The response includes `requestId` and `returnUrl` so the account UI can reopen the card panel.

With `partner_link`, use your existing Google/email guest login and preserve the request ID across that flow. A request ID cannot replace verification. If a different guest signs in, capture is denied without disclosing the invitation recipient.

## Open the card form and confirm completion

Call `guestBrowser.wallet.capture({ walletRequestId: invitation.id })` using `createKismetGuestBrowserClient` from `@kismet-tech/sdk/react`, or POST `{ walletRequestId: invitation.id }` to `handlers.walletCapture`. The handler derives `parentOrigin` from its own request; the Developer API verifies the authenticated guest, request, installation, collection and environment before preparing capture. Mount the returned `captureUrl` with `mountKismetGuestWalletCapture`. Card fields, provider secrets and payment credentials stay in the Kismet-hosted frame.

The frame's completion message is a prompt to refresh the account, not proof of a committed save. The authoritative `guest.wallet_request.completed` event is produced after the provider confirms success and Kismet commits the wallet/request and event together. Its data includes `request_id`, optional opaque `reference`, `payment_method_id` and `outcome` (`added` or `already_present`). A newly associated card can also produce `payment_method.added`.

Verify the signature over the exact raw body. Validate event version, type, installation, collection and environment. Persist the event ID atomically with your consumer action before returning 2xx. Retries and replay may deliver the same fact more than once. Events contain scoped guest references, not email addresses, global guest IDs, card numbers, provider IDs or authentication tokens.

## TEST acceptance before enabling guests

Use an approved test identity and a hosted partner test page, receive the actual Kismet invitation, save a Stripe TEST card through the hosted form, and verify both the request status and signed delivery at your HTTPS receiver. Confirm a duplicate callback or replay performs one consumer action, and that the event cannot be read using another installation or LIVE credential.

Also test expired/revoked invitations, resend invalidation, callback changes, wrong-user login and a blocked receiver. Local provider simulations and loopback deliveries are useful integration tests; they do not establish hosted provider acceptance or LIVE readiness.
