- the bank debit;
- the crypto delivery.
Source of truth
Use the successful create response for the first screen. After that, use current API reads:GET /v1/deposits/{depositId}for the bank and crypto lifecycle;GET /v1/funding-sources/{sourceId}for whether the bank can be used again;GET /v1/integrator/ach-debit/returns/{returnId}for return handling.
onramp.created, onramp.updated, onramp.completed,
achDebitReturn.created, and achDebitReturn.updated to synchronize current state.
Subscribe separately to achDebit.authorized, achDebit.deliveryProgress,
achDebit.delivered, achDebit.refunded, and achDebit.returned for push
notifications. Every webhook can arrive more than once or out of order.
For an onramp.* event:
- Fetch the current on-ramp by its webhook
id. - If
source.depositIdis present, fetch that deposit with the user’s authorization. - Update your stored resource and UI idempotently.
achDebit.* event’s immutable snapshot, stable eventId, and per-deposit sequence.
For an achDebitReturn.* event, fetch the return with integrator authentication. Use
its depositId and sourceId to refresh the related resources.
Amounts to display
releasedAmountUsd includes releases submitted to the blockchain but not necessarily
confirmed. Do not tell the user that amount was delivered. Use
confirmedReleasedAmountUsd for delivered amounts and payoutTxHash as proof when it
is available.
Deposit choice
Keep the amount screen simple. Offer at most two choices:
Do not expose W1/W2, risk tiers,
high.maxAmountUsd, or high.maxEarlyReleaseAmountUsd as
separate product concepts. Use the maximum for the selected priority only to validate
the amount. Then prepare the deposit and show the exact returned
instantPortionUsd, settlementPortionUsd, userFeeUsd, and totalDebitAmountUsd
before the user authorizes it.
For example, render a prepared 100 delivered
early; $500 after the bank debit settles.” The user chooses the delivery option, not
the split.
Also show the network and shortened wallet address. Use the funding source’s
institution.name and accountNumberLast4 when available; omit either value when it
is absent instead of showing a placeholder.
Shorten wallet addresses to the first 6 and last 4 characters, separated by … (for
example AbCd12…9XyZ). Keep the full address available to copy and in an accessible
label. Never use the shortened form as a unique key or identity check.
Format availableAt and clearsAt in the user’s app locale and configured timezone
(fall back to the device timezone), including a short timezone name. Use “after [date]”
for availableAt and exact clear times. When clearsAtIsEstimate is true, use
“around [date]” so the UI does not turn a forecast into a promise.
Use these grammatical bank labels in copy:
State machines
The top-levelstatus is useful for lists. The two component statuses explain what is
actually happening.
Bank debit
debitFailureCode and debitFailureReason are diagnostic provider values, not a closed
public enum. Log them for support. Do not build user copy or control flow around them.
Crypto delivery
releaseFailureCode and releaseFailureReason are diagnostic provider values. Do not
branch user copy on them.
Top-level deposit
Do not infer the component state from
status. For example, a deposit can have a
settled bank debit and a failed crypto release, or completed crypto delivery and a bank
debit that later returns.
Screens and messages
Use these states as the minimum complete frontend contract:
Do not call an authorized debit “withdrawn,” “submitted,” or “completed.” Do not call a
partial delivery complete. Keep completed deposits visible in history because a later
ACH return can change them.
For high-priority deposits, render
priority, releaseDecisionMode, both portions,
userFeeUsd, and totalDebitAmountUsd from the create response. They can differ
from the earlier preparation. A partially instant result or downgrade to normal is a
successful deposit, not an error.
Pre-deposit UI copy
These outcomes happen before a deposit exists. Show them in the active flow; do not send email or push. Replace bracketed values with current structured API fields. Private risk, provider, reserve, and integration-policy inputs are not returned; never infer them or put raw diagnostics in user copy. “Contact support” always means the support channel inside your app. Configure onesupportHref before launch and use it for every support CTA below. Your support team
escalates to Spritz with the user ID, funding-source/deposit ID when one exists, and the
response’s X-Request-ID / X-Correlation-ID. Do not send the end user to an
undocumented Spritz operations channel.
Bank linking and funding sources
Amount, availability, and deposit decisions
For
daily_limit, monthly_limit, unsettled_deposit_limit,
unsettled_amount_limit, bank_unsettled_deposit_limit, open_exposure, and
aggregate_exposure, re-read limitsByPriority and fill the copy from its public
maxAmountUsd, suggestedAction, and clearsAt. Do not expose which private control
produced that current limit.
Email and push notifications
Channel ownership for linked-bank ACH debit is fixed:- Spritz sends transactional emails for
achDebit.authorized,achDebit.deliveryProgress,achDebit.delivered,achDebit.refunded, andachDebit.returnedafter email delivery is enabled for your production integrator. - Your integration sends push notifications in your app from the same milestones.
- Your integration owns the durable in-app history and status UI.
onramp.updated. Persist
every semantic webhook eventId in a unique inbox and the highest accepted sequence
per deposit. In the same transaction, enqueue one push keyed by eventId. Duplicate
event IDs and sequences at or below the high-water mark produce no push. This prevents
both a repeated partial-delivery push and a stale partial push after full delivery.
Authorized
For push, handleachDebit.authorized. Its snapshot matches the successful create,
not the earlier preparation.
- Partial instant
- Full instant
- After settlement
Title: You authorized a $506.00 bank debit“You authorized a 500.00 of
USDC. 400.00 after the bank debit settles.
Fee: $6.00. No action needed.”
Partial delivery
HandleachDebit.deliveryProgress. Spritz creates a new occurrence only when
confirmedReleasedAmountUsd increases above zero but remains below
principalAmountUsd.
Title: Part of your USDC was delivered
When the debit is not settled:
400.00 remains pending until the bank debit settles. No action needed.When the debit is settled:
400.00 is being delivered. No action needed.Each legitimate increase has a new
eventId and higher sequence.
Full crypto delivery
HandleachDebit.delivered. Its immutable snapshot has releaseStatus: "completed"
and confirmedReleasedAmountUsd at least principalAmountUsd.
Title: Your USDC was delivered
If the debit is not settled:
Your full 506.00 bank debit is still processing. No action needed.If the debit is settled:
Your 506.00 bank debit settled. No action needed.When an instant deposit later settles without another delivery change, update the in-app bank status. A second email or push is usually unnecessary.
Failure and refund
For a release failure, state exactly how much was confirmed and how much was not delivered. Do not promise a refund merely because delivery failed. Failure copy is currently for the in-app state only: Spritz does not send a failure email, and your integration must not synthesize a push fromonramp.updated. Do not add an integrator
email or push for failures until Spritz publishes a semantic failure event.
Title: Your USDC delivery could not be completed
With nothing confirmed:
Your USDC delivery could not be completed. No USDC was delivered. Check the bank status shown with this deposit. Contact support if you need help.After a partial confirmation:
400.00 was not delivered. We’re looking into it. Contact support if you need help.For a debit failure before settlement, the deposit is terminal. Do not retry it or tell the user to retry the same authorization: Title: Your bank debit failed
Your $506.00 bank debit failed. No new debit was started. Contact support if you need help.Handle
achDebit.refunded; do not infer the notification from current deposit status:
Title: Your $506.00 bank debit refund was initiated
A $506.00 refund to Chase ••••6789 was initiated. Bank processing times vary. No action needed.
status: "refunded" and achDebit.refunded both mean the bank refund was initiated,
not that the user received it. This is the final public refund milestone; there is no
later refund-completed or refund-failed status or semantic event. Keep the deposit in
history with “initiated” wording, and route a reported non-receipt through support.
ACH return
HandleachDebit.returned. Use its immutable deposit and achReturn snapshots for
the push; use the resource webhooks and API reads separately to refresh the UI.
Title: Your bank returned an ACH debit
Build the body from exactly two parts: one crypto-state sentence, followed by one
userAction sentence. Calculate amounts from the event’s
deposit.confirmedReleasedAmountUsd; achReturn.cryptoStateAtReturn selects
the wording but is not itself an amount snapshot.
Never accuse the user of fraud or expose a return/risk reason.
Access restored
If support later restores access, update the in-app state only after fresh reads show all three:- the user’s
fiat_to_crypto/ach_debitcapability isactive; - at least one funding source is
active; and - that source’s
limitsByPriority.normal.availableistrue.
Keep pre-deposit outcomes in the active flow
Do not send email or push for an outcome that occurs before a deposit exists. Show it where the user is choosing a bank, amount, or authorization:- bank-link and ownership outcomes;
- validation and limit errors;
- funding-source eligibility errors;
- high priority reduced or changed to normal;
- temporary integration or program pauses.
suggestedAction, clearsAt, availableAt, and permanent when present. Do not
show internal decision details or suggest changing the amount to probe a decline.
The deposit API does not expose a stable expected-delivery deadline. Do not decide a
deposit is late from a local timer. Continue showing its current bank and crypto
state, and reconcile from the API after missed webhooks.
Frontend completion checklist
- Authorization shows the exact create-response debit, principal, fee, timing, and destination.
- Bank and crypto progress are rendered separately.
- Partial delivery shows confirmed and remaining amounts.
- A failed remainder is labeled “not delivered,” not “pending.”
- Normal, partial-instant, full-instant, and high-to-normal outcomes are supported.
- Returned and refunded deposits remain visible in history.
- Return handling branches on
userAction, not raw reason text. - Resource webhook processing reconciles current API state; semantic push processing permanently deduplicates
eventIdand rejects stalesequencevalues. - Spritz ACH email delivery is confirmed enabled; the integrator sends no duplicate emails and owns push.
- Every message gives the next action or says “No action needed.”
Related
Linked bank on-ramp
Build the complete backend and frontend flow.
ACH returns
Handle return classes and user access changes.
Webhooks
Verify, deduplicate, and reconcile deliveries.
Sandbox
Run deterministic scenarios and the documented contract-only cases before launch.