Skip to main content
The owner of a vaulted item can ask for it back. Your page sends the request from the owner’s Endstate wallet, and a waiting period starts. When it ends, the vault records the item’s dispatch with its own tap and Endstate completes the unvault. The owner acts once. Both calls need the same two things:
  • The owner’s wallet. Only a wallet that holds the item now can ask for it back or cancel that.
  • The vault request’s id. It names the vaulted item. Neither call takes a tap: the vault holds the item, so its owner cannot tap it.
Unvaulting is enabled per environment with vaulting; until it is, these calls answer not_found.resource.

Keep the vault request id

vault() resolves with the vault request. Save its id with the item on your server when the owner vaults, and hand it to the page that offers the unvault.
Do this at vault time. canVault names the request only until the item is vaulted, and the owner cannot tap a vaulted item, because the vault holds it, so the page has no later call that returns the id. Your server can still find it. vaultRequests.list() in @endstate-sdk/core returns a unit’s vault requests. At most one request of a unit is open at a time: it reads pending, submitted, active, unvault_waiting or unvaulting. From active on, the item is in the vault, so that request is the vaulted item’s.

The flow

  1. Ask for the item. requestUnvault({ requestId }) sends the owner’s request from their wallet and resolves once the network included it. A few seconds later Endstate records it: the vault request reads unvault_waiting, with available_at set to the end of the waiting period.
  2. Wait. The brand sets the waiting period for each vault it works with: never less than that vault’s own minimum, and up to 30 days. The vault uses it to prepare the item, and the item cannot change hands while it runs. The vault or the brand can place one hold with a reason. A hold stops the clock, and the time it lasted is added to available_at when it is lifted.
  3. Cancel, if the owner changes their mind. While the unvault waits, cancelUnvault({ requestId }) cancels it from the wallet. The item stays vaulted, the vault request reads active again, and the owner can ask again later.
  4. The vault dispatches the item. Once the waiting period has ended and nothing holds the unvault, the item is ready to ship. The vault records the dispatch with a tap of the item, Endstate completes the unvault, and the vault request reads unvaulted. The vault and the owner arrange delivery between them.
There is no call that completes an unvault. Endstate completes it after the vault’s dispatch tap. A cancel sent straight after the request can be refused with submission.not_executable, because Endstate records the request a few seconds after the network includes it, so show the refusal and let the owner try again after a moment. Render one UnvaultPrompt per vaulted item, with key={requestId}. Each hook holds the outcome of its latest call only, and data.resourceId names the vault request that outcome is for.

When cancel is refused

The owner can cancel from the moment they ask until the item is ready to ship, including while a hold is in place. After that the vault may already be packing the item, so cancelUnvault() rejects with submission.not_executable:
  • Ready to ship, not dispatched. error.details.cancellable_at is the time a cancel opens again: 7 days after the waiting period ended. If the vault has still not dispatched the item by then, the owner can cancel.
  • Dispatched, or already unvaulted. The error carries no cancellable_at. The unvault can no longer be cancelled.
Keys in error.details are the API’s own, so they stay in snake_case.

What the page can show

The page learns what its own calls report. requestUnvault() and cancelUnvault() resolve with { transactionHash, address, resourceId }, and a refusal says why the call could not go ahead. The vault request carries the rest: where the unvault is, when the waiting period ends, whether a hold stands and when the vault dispatched the item. Your server reads it with vaultRequests.get() in @endstate-sdk/core and hands the page what it shows.
Make this read on your server. It takes your secret key, which must never reach a browser, and it does not accept a tap session token. The owner cannot tap a vaulted item in any case, because the vault holds it. Decide who may see the answer before you return it. Endstate does not tell your server which shopper is asking, and its unit read names no owner, so use your own records: the account you saved the vault request id for when the item was vaulted. Return only what the page shows. The request also carries notes, the vault staff’s free text, and a hold’s reason; pass them on only if the owner should read them. The SDK returns the fields below in camelCase: availableAt, readyToShip, dispatchedAt.
string
active: the item is vaulted and no unvault is under way. unvault_waiting: the owner asked for the item, and it is waiting or ready to ship. unvaulting: the vault dispatched the item and Endstate is completing the unvault. unvaulted: the unvault is complete.
string | null
When the waiting period ends. Null until Endstate records the owner’s request, and again after a cancel. Lifting a hold moves it later by the time the hold lasted.
object | null
Set while a hold stands, else null. by is vault or brand, whoever placed it, and reason is their text.
boolean
True once the waiting period has ended and no hold stands. The vault can dispatch the item from then on. A cancel is refused from then until 7 days after available_at, when it opens again if the vault has still not dispatched the item.
string | null
When the vault recorded the item’s dispatch with its tap.
What to tell the owner at each step:

Follow an unvault

Webhooks announce each step, so your server does not have to poll. Subscribe an endpoint to vault_request.unvault_requested, vault_request.unvault_held, vault_request.unvault_hold_lifted, vault_request.unvault_dispatched, vault_request.unvaulted and vault_request.unvault_cancelled, and Endstate sends it the request, as this read returns it, when the owner’s request or cancel is recorded, when a hold is placed or lifted, when the vault dispatches and when the unvault completes. See Webhooks. No event is sent when the waiting period ends: the item is ready to ship at available_at if no hold stands. ready_to_ship in an event is its value when Endstate recorded the change, so compute it from available_at and hold. Reading the request stays available, for a page opened before an event arrived or a server with no endpoint. Read it when the owner opens the page, and while they watch it poll no more often than the page needs. Endstate records the owner’s request a few seconds after requestUnvault() resolves, so a read made straight away can still say active. Keep polling until it reads unvault_waiting. Stop polling when the request reads unvaulted: the unvault is complete and the request is closed. A request that reads active after it read unvault_waiting has no unvault under way, usually because the owner cancelled it, so there is nothing to follow until they ask again. Treat each read as the request’s current state, not as a one-way log. Rarely, the network revises its latest records, and a status then moves back: unvaulted to unvaulting or active, or unvault_waiting or unvaulting to active. Read the request again when the owner next opens the page instead of keeping unvaulted as final. Each such change sends a webhook event with a higher revision, and a type you already handled can arrive again, so act once per request, not once per event. A request that reads active again after the vault dispatched the item corrects the record; it does not mean the item is back in the vault. A hold does not survive the change and is placed again if the unvault is requested again. Corrections lists each case. This read is rate limited. Each response carries the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers. A refused read answers 429 rate_limit.exceeded with Retry-After: wait at least that long before the next read, and back off if it repeats. Limits are set per organization, as Errors describes. Poll from your server and share one answer between page views of the same item, not one read per view.

Outcomes and the wallet

Each hook returns { status, data, error, reset }, with status "idle" | "pending" | "success" | "error". It holds the outcome of its latest call, and data.resourceId names the vault request that outcome is for. A page that shows several vaulted items renders one component per item, with key={requestId}, so each item has its own outcome. An outcome belongs to the wallet that sent it and to the sign-in. Another wallet, a sign-out, a sign-in or reset() returns the hook to "idle", and a call still in flight for the old one never lands. A new tap does not reset these two hooks, unlike the vault hooks: an unvault takes no tap, so a tap of another item leaves its outcome in place. Both sends go from the owner’s wallet with the cost covered by Endstate; the owner never pays for them. A call resolves once the network included the send. Endstate records it a few seconds later, so a cancelUnvault() sent straight after requestUnvault() resolves can reject with submission.not_executable. Retry it after a moment. If the item changes hands while it is vaulted, the new owner’s wallet sends these calls and the previous owner’s is refused with auth.forbidden.

Errors

409
requestUnvault() found an unvault already under way or finished, or cancelUnvault() found nothing it can cancel: no unvault is waiting, or the item is ready to ship. error.details.cancellable_at, the time a cancel opens again, is present only while the item is ready to ship and not dispatched. Nothing was sent.
409
An earlier unvault of the item is still settling, so a new one cannot start yet. Retry after error.details.retry_at.
404
The id names no vaulted item in this environment: it is unknown, or its request never reached the vault because it is still pending or was cancelled, rejected or expired.
403
This wallet does not hold the item now.
frame
The send cannot be covered right now. Nothing was sent, and the owner is never asked to pay. Retry later.
404
Vaulting is not enabled for this environment.
See Errors for every unvault code, and EndstateWalletError for the wallet’s own.

See also

useRequestUnvault

The hook that sends the owner’s request to unvault.

Vault an item

How the item got into the vault, and where the request id comes from.

vaultRequests.get

The server-side read that carries the unvault’s state.