Skip to main content

What is a Deposit?

A deposit represents a single NGN bank transfer into an onramp’s virtual account. It is the upstream event that triggers FX conversion and settlement.
A deposit is not the on-chain transaction—it’s the NGN inflow. The on-chain withdrawal (if applicable) happens after FX conversion.

Deposit Properties

PropertyDescriptionExample
deposit_idUnique identifierdep_9x2k5m8p
onramp_idParent onramponramp_3j5k8n2q
merchant_idOwner (inferred)merch_abc123
user_emailEnd-user email[email protected]
payment_referenceDaya-generated referenceDAYA-3J5K8N2Q
amount_ngnNGN amount received15000.00
bank_referenceBank’s transaction referenceFLW-REF-123456
rate_idFX rate usedrate_8x7k2mq9p
amount_assetAsset amount after conversion9.70
assetAsset typeUSDC, USDT
chainBlockchain networkBASE, ETH, etc.
tx_hashOn-chain transaction hash0x8f3e2d...
statusCurrent stateSee Deposit Statuses
failure_reasonWhy deposit failed (if applicable)"Rate expired"
flag_reasonWhy flagged (if applicable)"Deposit after expiry"
created_atWhen deposit received2026-01-14T15:06:30Z
settled_atWhen fully settled2026-01-14T15:08:15Z
updated_atLast status change2026-01-14T15:08:15Z
Bank sender details (sender_name, sender_account, sender_bank) are captured internally but not exposed via API in v0.1.

Deposit Statuses

StatusDescriptionTerminal?Merchant Action Required?
PENDING_FXNGN received, awaiting FX execution❌ No❌ No
PENDING_WITHDRAWALFX done, waiting for on-chain confirmation❌ No❌ No
SETTLEDFully complete (on-chain or balance credit)✅ Yes❌ No
FAILEDPermanent failure✅ Yes✅ Contact support
FLAGGEDHeld for manual review✅ Yes✅ Contact support
REVERSEDBank reversed the transfer✅ Yes❌ No

Terminal vs Non-Terminal States

Non-terminal states (in-progress):
  • PENDING_FX → FX conversion in progress
  • PENDING_WITHDRAWAL → On-chain broadcast in progress
Terminal states (final):
  • SETTLED → Success
  • FAILED → Permanent failure
  • FLAGGED → Requires manual ops review
  • REVERSED → Bank reversed the original transfer
Deposits in FAILED, FLAGGED, or REVERSED states are not counted as Daya-owned funds and are excluded from treasury calculations.

Deposit Lifecycle

Lifecycle Stages

NGN deposit received, FX not yet executedTriggers:
  • Bank transfer lands in virtual account
  • Webhook from payment provider received
Next steps:
  • Validate deposit is within onramp TTL and rate expiry
  • Check limits (per-onramp, per-merchant, system-wide)
  • Execute FX conversion
Webhook: deposit.created

Idempotency

Duplicate bank transfers with the same bank_reference create only one deposit record. Why?
  • Banks may send duplicate webhooks
  • User may accidentally transfer twice to same VA
  • Payment provider may retry notifications
Guarantee: Each unique bank_reference maps to exactly one deposit_id.
Your webhook handler should also implement idempotency by checking event_id or deposit_id to avoid processing the same event twice.

Querying Deposits

List Deposits

Retrieve deposits with optional filters:
GET /v1/deposits?onramp_id=onramp_123&status=SETTLED&from=2026-01-01&to=2026-01-31
Query Parameters:
ParameterDescriptionExample
onramp_idFilter by onramponramp_3j5k8n2q
statusFilter by statusSETTLED, PENDING_FX
fromStart date (ISO 8601)2026-01-01T00:00:00Z
toEnd date (ISO 8601)2026-01-31T23:59:59Z
pagePage number1
per_pageResults per page20 (max 100)
See List Deposits API.

Get Single Deposit

Retrieve specific deposit by ID:
GET /v1/deposits/{deposit_id}
See Get Deposit API.

Example Responses

{
  "deposit_id": "dep_9x2k5m8p",
  "onramp_id": "onramp_3j5k8n2q",
  "payment_reference": "DAYA-3J5K8N2Q",
  "user_email": "[email protected]",
  "amount_ngn": "15000.00",
  "status": "SETTLED",
  "rate_id": "rate_8x7k2mq9p",
  "amount_asset": "9.70",
  "asset": "USDC",
  "chain": "BASE",
  "tx_hash": "0x8f3e2d1c0b9a8e7f6d5c4b3a2e1f0d9c8b7a6e5f4d3c2b1a",
  "created_at": "2026-01-14T15:06:30Z",
  "settled_at": "2026-01-14T15:08:15Z",
  "updated_at": "2026-01-14T15:08:15Z"
}

Atomicity Guarantees

FX conversion and settlement are atomic:
  • Either: Both FX and settlement succeed → SETTLED
  • Or: One or both fail → FAILED or FLAGGED
Never possible: Partial FX (e.g., NGN debited but USDC not credited) This is enforced via double-entry ledger accounting. All money-affecting operations create compensating ledger entries.

Common Deposit Flows

  1. User transfers 15,000 NGN to virtual account
  2. Deposit created: PENDING_FX
  3. FX executed: 15,000 NGN → 9.70 USDC
  4. Risk engine approves withdrawal
  5. Status → PENDING_WITHDRAWAL
  6. On-chain transaction broadcast to BASE
  7. Transaction confirms
  8. Status → SETTLED
  9. Webhook: deposit.settled
Duration: 2-5 minutes

Merchant-Visible Fields Only

The following fields are captured internally but not exposed via API:
  • sender_name (bank sender’s name)
  • sender_account (sender’s account number)
  • sender_bank (sender’s bank code)
  • Internal risk scores
  • Detailed risk engine decision logs
If you need sender details for support or compliance, contact Daya operations with the deposit_id.

Best Practices

1

Use webhooks, not polling

Set up webhooks for real-time updates instead of polling GET /v1/deposits.
2

Handle all terminal states

Your application should gracefully handle SETTLED, FAILED, FLAGGED, and REVERSED.
3

Store deposit_id with user records

Link deposit_id to your user’s transaction history for easy lookup.
4

Implement idempotency in webhooks

Use event_id or deposit_id to deduplicate webhook deliveries.
5

Show tx_hash to users

Display tx_hash (if available) so users can verify on-chain settlement on block explorers.

Next Steps