Skip to main content
A card can be funded from a customer’s Embedded Wallet: an EMBEDDED_WALLET internal account that holds USDB and that only the customer can sign for. Grid cannot debit that account the way it debits a custodial funding source, and a card authorization has to be answered in seconds, so the cardholder cannot sign each purchase. Instead the cardholder authorizes a delegated key once. Grid custodies the key and uses it to sign the pull from the wallet at every authorization. This page covers issuing the card, creating the delegated key, what happens when an authorization arrives, and what fails when the card has no active key.

Prerequisites

  • Cards are enabled for your platform, and your Lightspark contact has enabled Embedded Wallet card funding for it.
  • The cardholder is a Customer with kycStatus: APPROVED and has an EMBEDDED_WALLET internal account. To find it, list the customer’s internal accounts with GET /internal-accounts?customerId=... and pick the account with type: "EMBEDDED_WALLET".
  • The wallet is provisioned. Grid provisions the wallet’s signer the first time a credential on the account is verified, so the cardholder must have completed at least one credential verification. See Authentication.
  • Your client can hold a session signing key and stamp a payloadToSign. Creating the delegated key needs two stamps from the cardholder’s active session. See Client keys.
  • The wallet holds USDB on Spark. Each authorization spends the wallet’s on-chain balance at authorization time.

How the pieces fit

  1. You issue the card with the wallet account as its fundingSource.
  2. The cardholder authorizes a delegated key for that card and account through a three-leg signed-retry flow. Grid generates the keypair, registers it as a signer on the wallet, and keeps the private key.
  3. When an authorization arrives, Grid runs its usual decisioning, then pays the hold from the wallet: it builds a USDB transfer for the authorized amount, signs it with the delegated key, and broadcasts it on Spark.
  4. Clearings capture the held USDB. Reversals and expiries return it to the wallet. Neither needs the cardholder.

Issue the card

Issue the card the same way as any other, with the Embedded Wallet account as the funding source:
The card’s currency is USD. The wallet holds USDB, which Grid converts 1:1, so a $12.50 authorization pulls 12.50 USDB. The card comes back in status: "PROCESSING" and activates through the CARD.STATUS_CHANGE webhook as described in Issuing cards. An ACTIVE card with no active delegated key declines every authorization, so complete the next section before you show the card to the cardholder. Set maxSpendPerTransaction and maxSpendPerDay. Spend controls work the same way for a wallet-funded card as for any other card: the values on the card cap what that card can authorize, and cardConfigs in your platform config can set platform-wide caps that apply to every card you issue. When both are set, Grid enforces the lower of the two.

Create the delegated key

POST /auth/delegated-keys is a three-leg flow. Every leg sends the same body; the second and third legs add a stamp from the cardholder’s session signing key. Your backend relays each payloadToSign to the client and the resulting stamp back to Grid, the same shape as passkey registration.

Leg 1: request the challenge

Send the request with no signature headers. cardId and internalAccountId identify the card funding source; the account must be the card’s current fundingSource.
Grid generates the keypair and returns 202:
Each challenge expires five minutes after it is issued. If the cardholder takes longer, start again from leg 1.

Leg 2: authorize the signer

The client stamps payloadToSign byte for byte with the session signing key. Retry the same request with the stamp in Grid-Wallet-Signature and the requestId in Request-Id:
Grid registers the key as a signer on the wallet and returns a second 202 with a new payloadToSign and requestId. The key now exists in PENDING status: the signer is registered but holds no signing policy yet, so it cannot sign.

Leg 3: grant the signing policy

The client stamps the new payloadToSign. Retry once more with the new Request-Id. The response is 201 with the key in ACTIVE status:
The private key never leaves Grid. publicKey identifies the credential and is not needed for anything else.

Rules

  • A card funding source has at most one ACTIVE key. A second create returns 409 CONFLICT until you revoke the first.
  • A flow abandoned after leg 2 leaves a PENDING key. It cannot sign and does not block creating another key.
  • The key does not expire. It stays ACTIVE until you revoke it.
  • The key is bound to the pair of card and funding account. If you replace the card’s fundingSource with a different account, create a new key for the new pairing and revoke the old one.

Errors

What happens at authorization

When the network asks Grid to authorize a purchase on the card:
  1. Grid applies the same checks as for any card: the card is ACTIVE, the amount is within the card’s and the platform’s spend limits, and program rules allow the merchant.
  2. Grid builds a USDB transfer from the wallet for the authorized amount, signs it with the delegated key, and broadcasts it on Spark. The wallet’s balance drops by the authorized amount now, not at clearing.
  3. Grid approves the authorization once the broadcast is accepted and sends CARD_TRANSACTION.AUTHORIZED. accountId on the transaction is the wallet account.
Later events on the transaction:
  • Clearing. Grid captures the held USDB. The wallet is not touched again for the authorized amount. If the merchant clears for more than the authorization, Grid pulls the difference from the wallet the same way it pulled the hold, signed with the delegated key.
  • Reversal or expiry. Grid returns the held USDB to the wallet on Spark. The return is a separate on-chain transfer into the wallet and does not use the delegated key.
Two behaviors differ from a custodial funding source:
  • The balance check is real and synchronous. Grid selects the wallet’s USDB outputs before it signs. If the wallet cannot cover the amount, the authorization is declined, and no hold is placed.
  • Approval waits on the broadcast. The authorization is approved only after the signed transfer has been accepted by the Spark network.

What fails without an active key

Grid checks for the delegated key when it goes to sign the pull, not before. If the card’s funding source has no ACTIVE key (never created, still PENDING, or revoked), the signing step fails and Grid declines the authorization. Nothing is pulled from the wallet. The card transaction is recorded with status: "DECLINED" and cardDeclinedReason: "OTHER", and a CARD_TRANSACTION.DECLINED webhook fires. The decline does not name the missing key, so check for it directly when you investigate a declined card funded from a wallet:
An empty data array, or one with no ACTIVE entry, means every authorization on that card declines until the cardholder completes the three-leg flow again. The same decline path applies when the cardholder revokes the key: the card stays ACTIVE, and every authorization declines. To stop a card from spending without touching the key, freeze it with PATCH /cards/{id} and status: "FROZEN".

Manage the key

List the keys for an account or a funding source. At least one filter is required. PENDING and REVOKED keys are included:
Revoke a key. No signature is needed; Grid uses the custodied key to remove its own signer from the wallet:
The response is 204. Revoking a key that is not ACTIVE returns 400 INVALID_INPUT. Revocation is permanent. To let the card spend again, run the three-leg flow to create a new key. Revoke the key when the card is closed or when the cardholder asks to withdraw Grid’s authority over the wallet. Closing the card does not revoke the key.

Testing in Sandbox

The flow is the same in Sandbox. Sandbox wallets run on Spark regtest, and a sandbox authorization pulls regtest USDB from the wallet into a sandbox treasury.
  • Sandbox accepts a real session stamp on the delegated-key legs. The legacy value Grid-Wallet-Signature: sandbox-valid-signature is also accepted.
  • POST /sandbox/internal-accounts/{id}/fund credits the account’s ledger balance only. A card hold spends the wallet’s balance on Spark, so fund the wallet with a quote whose destination is the wallet account, as described in the Global Accounts walkthrough, before you simulate an authorization.
  • POST /sandbox/cards/{id}/simulate/authorization with any non-magic descriptor drives the pull. Run it once with the key in place and once after revoking the key to see both outcomes.
See Sandbox testing for the simulate endpoints and the descriptor suffixes.