/Platform
Get started Build your flow Core concepts API
Language

What is Moneva Platform?

KYC onboarding Virtual accounts Deposits FX
Payouts Remittance Webhooks Settlement

Moneva Platform is the account layer for your product: onboard a customer with hosted KYC, give them named virtual fiat accounts, and move money in and out across 130+ countries on stablecoin rails. One REST API, one secret key, everything event-driven.

Features

Customers & KYCOne object for your end user. Hosted verification, webhook-driven status, no document handling on your side.
Virtual accountsNamed USD, EUR, GBP and NGN deposit details per customer. Every deposit converts and settles as USDC.
Payouts & remittancePayouts to 130+ countries: bank rails like SEPA and NIBSS, instant systems like Pix, mobile money like M-Pesa, plus SWIFT reach.
Bank forms as an APIPer-corridor beneficiary schemas served by the API. Render the exact form; validation can never drift.
Signed webhooksEvery state transition is an HMAC-signed event with retries and a queryable 90-day log. Nothing is dropped.
Instant sandboxSelf-serve test keys against a deterministic simulator. Force any KYC outcome, deposit or transfer state.
Any-crypto funding In developmentCustomers fund with any supported token on any chain; the built-in swap layer settles it as USDC before payout.

How it works

Your backend talks to one REST API with one secret key. Underneath, Moneva orchestrates licensed banking, compliance and settlement partners. Their sessions, retries, reconciliation and corridor quirks are handled inside the platform and never surface in your integration.

Self-custody, always. Every customer balance settles to a wallet only they control: their own wallet or the embedded wallet of your app. Moneva never generates, stores, or has access to keys or funds.

KYC and regulatory compliance are owned by licensed partners. Your product embeds a hosted verification flow and reads back a clean status. You never store documents or make compliance decisions.

Status: the sandbox is live and self-serve today. Live mode is in development. Pricing: flat monthly platform fee plus a per-verification fee, with no basis points on volume from Moneva.

Next steps

Your first payout in 15 minutes

1. Create a customer

The customer starts in kyc_required. The wallet address is the customer's own; Moneva is self-custody only.

2. Register your webhook

Everything that happens next arrives as signed events. Register an endpoint once; the signing secret is returned only on creation.

No endpoint yet? GET /v1/events serves the same events as a queryable log.

3. Verify identity

Create a hosted KYC session and open the returned URL in your product. In test mode, force the outcome instead:

Your webhook receives customer.kyc.approved; the customer is active.

4. Give them an account

Provisioning is asynchronous: 202 now, then account.activated delivers the IBAN.

5. Simulate a deposit

6. Add a bank destination

Fetch the corridor's form schema, render it, submit the fields:

7. Quote, then transfer

Watch transfer.created then transfer.processing arrive on your webhook, then force the terminal state with POST /v1/test/transfers/{id}. That is the whole integration.

Next steps

Integrate your way

The ladder

REST API LiveCurl-first, OpenAPI as the source of truth. Idempotency, signed webhooks, stable error codes. Download the spec.
TypeScript SDK BetaIn the repo today (sdk-ts): zero-dependency typed client, webhook verification, and the one-call wallet-session prompt. Tested end to end against the gateway; npm publish pending.
React components & hooks BetaHeadless bindings (sdk-react): useCorridors, useBeneficiarySchema, useProfileSchema, and a <BeneficiaryForm> that renders itself from the corridor schema. Bring your own styling.
Webhook adapters BetaShipped inside the SDK: verifyWebhook, an Express handler, and a Next.js route helper, with timing-safe signature checks and typed events.
AI agents & LLMs LiveMachine-readable everything: llms.txt, llms-full.txt (the whole docs as plain text), the OpenAPI spec, and key-less public reference endpoints.

The SDK, as implemented

Zero dependencies, field names identical to the REST API, every call typed:

import Moneva, { verifyWebhook } from "@moneva/platform";

const moneva = new Moneva(process.env.MONEVA_KEY);

const customer = await moneva.customers.create({
  email: "maria@example.com", first_name: "Maria", last_name: "Silva",
  country: "BR", wallet: { address: userWallet },
});

await moneva.walletSessions.prompt(customer.id, window.ethereum);  // one call

const quote = await moneva.quotes.create({ customer_id: customer.id,
  source: { currency: "USDC", amount: "100.00" }, destination_currency: "BRL" });

await moneva.transfers.create({ ... }, { idempotencyKey: order.id });

const event = verifyWebhook(rawBody, req.headers["moneva-signature"], secret);

For AI agents

Point a coding agent at /llms.txt and it gets the full picture: every docs page as text, the spec, the event catalog, and the public corridor and jurisdiction endpoints it can call without a key. The playground's copy-page button serves the same content per page for humans pasting into a chat.

One source of truth. The SDKs, components, docs, and agent bundles are all generated from openapi.yaml and the corridor registry. When a corridor or field changes, every integration path updates together.

Next steps

Authentication

Authorization: Bearer mk_test_4f8a...   # test mode
Authorization: Bearer mk_live_9c2d...   # live mode (in development)
Key prefixModeBehavior
mk_test_TestDeterministic simulator. Instant, free, resettable. Data is fully isolated from live.
mk_live_LiveIn development Returns live_mode_disabled today.

Secret keys grant full account access. Keep them server-side, never in client code, and rotate immediately if exposed.

Getting and rotating keys

  • Test keys are issued when your organization is created. The self-serve developer dashboard is in development; until it ships, keys are provisioned by Moneva.
  • Rotation: keys can be revoked and reissued without downtime; old keys stop authenticating immediately on revocation.
  • Scope: a key belongs to one organization and one mode. Test and live data are fully isolated.

Auth errors

CodeStatusWhen
invalid_api_key401Missing, malformed, unknown or revoked key
live_mode_disabled403Live key used while live mode is in development
test_mode_only403Live key calling a /v1/test/* simulation endpoint

Next steps

Customers & KYC

Create a customer

FieldNotes
emailRequired. Unique per organization and mode; a duplicate returns 409 customer_exists with the existing id
first_name / last_nameRequired. Latin character sets; punctuation is normalized before hitting banking rails
countryRequired. ISO 3166-1 alpha-2 residence; drives jurisdiction rules below
wallet.addressRequired. The customer's own wallet: external or the integrator's embedded wallet (passkey smart accounts work well). Self-custody only; Moneva never generates or stores keys
metadataOptional, up to 20 string keys, returned on every read

Lifecycle

created kyc_required kyc_pending active
StatusMeaningWhat to do
kyc_requiredVerification not startedCreate a KYC session and open url
kyc_pendingUnder reviewWait for customer.kyc.approved or .rejected
activeVerifiedAll money movement available
rejectedFailed permanentlyDo not retry; inform the user
suspendedCompliance holdContact support

Profile data

Before documents, KYC needs the "tell us about yourself" data: date of birth, address, occupation, and per-country extras. Two ways to collect it; mix them freely.

  • Hosted (default): do nothing. The hosted verification flow collects whatever is missing.
  • Your own form: render it from the per-country schema, exactly like bank forms, and submit via PATCH. Your branding, our rules.
curl https://platform.moneva.io/v1/jurisdictions/BR/profile_schema

{
  "country": "BR",
  "fields": [
    { "key": "date_of_birth", "type": "date", "required": true, "sensitive": false },
    { "key": "occupation", "type": "text", "required": true, "sensitive": false },
    { "key": "address.line1", "type": "text", "required": true, "sensitive": false }, …,
    { "key": "address.country", "type": "text", "required": true, "sensitive": false }
  ]
}
  • Per-country extras are schema-driven: US adds phone and ssn, Nigeria adds bvn. Your form never hardcodes any of it.
  • Sensitive ids are stored masked. SSN and BVN are forwarded for verification and kept as kind plus last four only.
  • Identity fields lock after verification: changing them on an active customer returns 409 profile_locked.
  • Updates emit customer.updated.

Wallet sessions

Self-custody is the only model: funds settle to a wallet only the customer controls. When an operation needs the customer's authorization, the gateway prepares a message, their wallet signs it client-side, and we verify. Two calls, one wallet prompt.

  • Sessions last six days. customer.wallet_session.active confirms activation; when an operation needs a fresh signature you get 409 wallet_session_required.
  • Any EIP-1193 wallet works: external wallets or embedded ones. The SDK wraps both calls and the wallet prompt in one helper.
  • EOAs and smart accounts both verify. A 65-byte ECDSA signature verifies offline; smart-contract accounts (Safe, Biconomy Nexus) verify on-chain via ERC-1271, including ERC-6492 counterfactual accounts that are not deployed yet.
  • Moneva never holds keys. The platform prepares and verifies messages; signing happens only on the customer's device.

Supported jurisdictions

Which virtual accounts and payouts a customer can use depends on their verified residency. The rules below are reference data from GET /v1/jurisdictions; per-customer eligibility is enforced automatically at KYC.

Create the customer with any of these residence countries; everything outside the list is rejected at onboarding with a clear error, before any KYC session is started.

Hosted verification

Open the URL in a webview or new tab. Progress arrives as customer.kyc.* events; you never receive or store identity documents.

Next steps

Virtual accounts

Create an account

  • KYC first. The customer must be active; otherwise 422 kyc_not_passed.
  • One account per currency. A duplicate returns 409 account_exists with the existing id.
  • Statuses: provisioningactive (details ready) · closed (provisioning failed or retired).
  • Eligibility follows the customer's residency; see Supported jurisdictions.

Provisioning is asynchronous

Account creation returns 202 with status provisioning. When the account is ready, typically within seconds, account.activated delivers the full deposit details. Never poll; consume the event.

{
  "object": "account",
  "currency": "EUR",
  "status": "active",
  "deposit_details": {
    "iban": "DE89 3704 0044 0532 0130 00",
    "bic": "…",
    "bank_name": "…"
  }
}
CurrencyDeposit details
EURIBAN + BIC (SEPA)
GBPAccount number + sort code (Faster Payments)
USDAccount + routing number (ACH)
NGNLocal account number

Deposits

Inbound fiat produces two events: account.deposit.received when the transfer is detected, and account.deposit.settled when converted proceeds land at the customer's settlement address.

Settlement is always USDC. Whatever currency arrives on a virtual account, the customer's balance is credited in USDC after conversion. Payouts then draw from that USDC balance. For NGN, funding flows mainly through stablecoins today; the local rail complements the crypto path.

Next steps

Corridors & bank forms

The beneficiary schema

curl https://platform.moneva.io/v1/corridors/BRL/beneficiary_schema

{
  "currency": "BRL",
  "transfer_methods": ["pix"],
  "fields": [
    { "key": "account_holder_name", "type": "text", "required": true },
    { "key": "pix_key", "type": "text", "required": true },
    { "key": "tax_id", "type": "digits", "required": true }
  ]
}

Render your bank form from fields. On submit, the same registry entry that produced the schema validates the values, so the form you rendered and the validation applied can never disagree. Rejections come back per field with stable codes.

The corridor object

curl https://platform.moneva.io/v1/corridors

{{
  "data": [
    {{ "currency": "BRL", "country": "BR", "transfer_methods": ["pix"],
      "minimum_usd": "5.00", "status": "active" }},
    …
  ]
}}

Public reference data. GET /v1/corridors, the beneficiary schemas, and GET /v1/jurisdictions need no API key and allow browser requests, so coverage pages and pre-signup flows can read them directly.

Currency availability

Live from GET /v1/jurisdictions and GET /v1/corridors: which currencies can come in as virtual-account deposits and which can go out as payouts.

Payout methods

Not every corridor is a bank account. The beneficiary schema tells you how each destination is addressed, so your form always collects the right thing:

Bank railsSEPA, Faster Payments, ACH, NIBSS, SPEI, IMPS, Zengin and SWIFT. Addressed by IBAN, account and routing numbers, CLABE, CBU or IFSC.
Instant payment systemsPix and similar alias-based rails. Addressed by a key such as an email, phone number or tax id, not an account number.
Mobile moneyM-Pesa, MTN MoMo and similar. Addressed by a mobile number plus provider.
WalletsGCash and similar. Addressed by a wallet number and provider.

The transfer_methods array on each corridor plus the served field set encode all of this; your integration never branches on method type by hand.

Normalization included

Common real-world failures are absorbed before they reach the banking rails: IBANs are uppercased and de-spaced, names are checked against the character sets the rails actually accept, and per-corridor formats are enforced up front.

Corridor availability and live minimums come from GET /v1/corridors. It reflects real capability, not a static table.

Next steps

Quotes

Create a quote

{
  "id": "qt_8d3f6a",
  "object": "quote",
  "rate": "5.067400",
  "fees": [{ "type": "transfer_fee", "amount": "1.40", "currency": "USD" }],
  "net_receivable": { "amount": "535.40", "currency": "BRL" },
  "minimum_usd": "5.00",
  "expires_at": "2026-07-23T00:41:02Z"
}

Quotes price payouts from the customer's USDC balance. Inbound deposits need no quote; they convert automatically at settlement.

How the price composes

One formula, no hidden components: net_receivable = (source amount − fees) × rate. The rate is mid-market; Moneva's margin is the visible transfer_fee line. For the example above: (100.00 − 1.40) × 5.0674 = 535.40 BRL, delivered exactly.

  • Net receivable is exact. What the quote says is what the beneficiary receives.
  • Minimums are live. Amounts below minimum_usd are rejected with amount_below_minimum and the current floor in details.
  • Quotes expire. Execute within expires_at (60 seconds) or create a new one.

Fields

FieldMeaning
rateMid-market FX rate, source to destination, six decimals
fees[]Itemized fee lines; today a single transfer_fee in USD
net_receivableExact amount and currency the beneficiary receives
minimum_usdLive corridor floor at quote time
expires_atExecution deadline; the transfer must be created before it

Errors

CodeWhen
amount_below_minimumAmount under the live corridor floor; details.minimum_usd carries it
unsupported_corridorNo active corridor for destination_currency
unsupported_source_currencyQuotes price from USDC/USD today
quote_expiredRaised at transfer creation when expires_at has passed
quote_beneficiary_mismatchQuote currency differs from the beneficiary's corridor

Using a quote in a transfer

  • Pass quote_id on POST /v1/transfers; the quote's rate, fees and net receivable are frozen onto the transfer at creation and never rewritten by settlement.
  • The quote's destination currency must match the beneficiary's corridor, or the transfer is rejected with quote_beneficiary_mismatch.
  • Display the quote to your user, then execute inside the 60-second window; on expiry, re-quote and re-confirm.

Next steps

Transfers

Create a transfer

{
  "id": "tr_4c7m9k",
  "object": "transfer",
  "type": "remittance",
  "status": "created",
  "destination": { "currency": "BRL", "amount": "535.40", "beneficiary_id": "ben_5j8w2e" },
  "fees": [{ "type": "transfer_fee", "amount": "1.40", "currency": "USD" }],
  "timeline": [{ "status": "created", "at": "…" }]
}

List with GET /v1/transfers?customer_id=…&status=…; retrieve a single transfer for the full timeline.

Types

TypeMeaning
payoutBank/method payout to a beneficiary in the account's currency zone
remittanceCross-currency payout against a quote
depositInbound funding recorded by the platform (never created by you)

Idempotency, required

Every transfer creation requires an Idempotency-Key header. Retries with the same key return the original result; the same key with a different payload is rejected with 409 idempotency_key_reused. Network failures can never double-pay.

Frozen quote values

The quote's rate, fees and net receivable are copied onto the transfer at creation. What you display to your user is what the record says forever; settlement never rewrites history.

States

created processing completed
also terminal: failed returned

Every transition is its own event with the full object attached, and the timeline array records each step with timestamps. failed always carries a machine-readable failure_reason and funds return to source.

Errors

CodeWhen
idempotency_key_requiredMissing Idempotency-Key header
idempotency_key_reusedSame key, different payload
idempotency_in_progressOriginal request still processing; retry shortly
kyc_not_passedCustomer is not active
beneficiary_not_ownedBeneficiary belongs to a different customer
quote_expiredQuote's expires_at has passed
quote_beneficiary_mismatchQuote currency differs from the beneficiary's corridor

Next steps

Events & webhooks

Verify signatures

const [t, v1] = header.split(",").map(p => p.split("=")[1]);
const expected = crypto
  .createHmac("sha256", endpointSecret)
  .update(`${t}.${rawBody}`)
  .digest("hex");
if (v1 !== expected || tooOld(t)) reject();   // 5-minute replay window

The signature arrives in the Moneva-Signature header as t=<unix>,v1=<hmac>. The endpoint secret is shown once at registration.

Delivery semantics

  • At-least-once. Deduplicate on event.id.
  • Every transition delivered. Intermediate states are never collapsed away by retries.
  • Retries back off over 72 hours; persistently failing endpoints are disabled and you are notified.
  • Recovery: GET /v1/events is the durable log; reconcile any gap from it.

Manage endpoints

  • Omit event_types to receive everything; list and delete endpoints with GET / DELETE /v1/webhook_endpoints/{id}.
  • Multiple endpoints per mode are supported; each has its own secret.

Reading the log directly

Cursor pagination via next_cursor; events are retained 90 days. The log is the recovery path after any webhook outage.

Event catalog

EventFired when
customer.createdCustomer record created
customer.kyc.pendingVerification started
customer.kyc.approvedCustomer is active
customer.kyc.rejectedVerification failed permanently
customer.wallet_session.activeCustomer-signed wallet session active
customer.updatedProfile or metadata updated
account.activatedDeposit details ready
account.deposit.receivedInbound fiat detected
account.deposit.settledProceeds settled to the customer
beneficiary.activatedBank destination payable
transfer.created / .processingTransfer accepted / funds in flight
transfer.completedFiat delivered
transfer.failed / .returnedTerminal failure / returned by receiving bank

Next steps

Email notifications

Your users expect to hear "your transfer arrived" from you, not from us. Two ways to make that happen, and both are first-class:

ApproachHowWhen to choose it
Build your ownConsume the webhook events and notify through your own stack: email, push, SMS, in-appYou already have notification infrastructure, or want channels beyond email
Platform-sent emailsEnable branded emails below; the platform sends on your behalf, in your brand, from your domainYou want status emails live today with zero email infrastructure

Everything on this page is optional and off by default. The email system is a convenience layered on the same events you receive anyway; nothing here is required, and skipping it costs you no functionality. Mix freely: many integrators send their own marketing and product mail but let the platform handle transactional status receipts.

Configure once

  • Off by default. Nothing is sent until you flip branded_emails.
  • Pick your events. Leave event_types empty to email on every notifiable event, or select a subset.
  • Your brand carries. Logo, accent color, sender name, reply-to, and footer are all yours.

Send from your own domain

By default mail leaves from the shared domain. Add your own domain and verify it via DNS, and every email is sent from your address: the end user never sees anything but your brand, down to the sender domain.

The response carries the DNS records to add at your DNS host:

TypeHostValue
TXT_moneva.acme.commoneva-verify=tok_…
CNAMEmnv1._domainkey.acme.commnv1.dkim.moneva.io
CNAMEmnv2._domainkey.acme.commnv2.dkim.moneva.io
CNAMEbounce.acme.combounce.moneva.io
  • Then set your address: PATCH /v1/notification_settings with from_email: "no-reply@acme.com". Setting it requires the domain to be added first.
  • Fail-safe: an unverified domain is never used; until verification passes, sends fall back to the shared domain. notification.domain.verified fires when DNS checks out.
  • Test mode verifies immediately so you can build the full flow against the sandbox; live mode resolves your real DNS records.

What gets sent

EventEmail
customer.kyc.approvedYou're verified
customer.kyc.rejectedWe couldn't verify your identity
account.activatedYour account is ready
account.deposit.settledYour deposit has settled
transfer.completedYour transfer was delivered
transfer.failedYour transfer could not be completed

Fully custom templates

The default layout carries your brand tokens. When that is not enough, replace it entirely: your own subject line and your own complete HTML per event. Your design system, your copy, your language.

  • Variables: {{sender_name}} everywhere; money events add receipt values like {{amount_sent}}, {{delivered}}, {{fee}}, {{reference}}, {{amount_received}}, {{settled}}. Values are HTML-escaped on insert; unknown variables stay visible so mistakes surface in preview.
  • Per event: override only the emails you care about; the rest keep the default layout. DELETE the template to revert.
  • Preview works the same: GET /preview renders your custom template with sample values before anything is enabled.

Preview before enabling

Returns the rendered subject and html with your current branding applied, so you can review every template before a single mail goes out. Try it below: the same template, rendered live with your brand.

Event
Sender name
From address
no-reply@acme.example
your verified domain
no-reply@notifications.moneva.io
shared fallback
Brand color
This is the default template GET /preview returns; logo, color, sender, domain, and footer are yours. Need more? Replace the whole layout per event with your own HTML via PUT /templates/{event_type}.
ACME Pay <no-reply@acme.example>
To: sam@example.com

Every send is observable

Each qualifying send appears in your event stream as notification.email.sent with the triggering event_type, the recipient, and the subject. In test mode no real mail leaves; the events fire exactly as they will in production, so you can build and verify the full loop against the sandbox.

Errors

{
  "code": "amount_below_minimum",
  "message": "Minimum for BRL is $5.00.",
  "details": { "minimum_usd": "5.00" }
}
CodeStatusMeaning
invalid_api_key401Missing, malformed or revoked key
live_mode_disabled403Live keys not yet enabled
resource_not_found404Unknown id for this org and mode
idempotency_key_required400Money POST without an Idempotency-Key
idempotency_key_reused409Same key, different payload
kyc_not_passed422Customer must be active first
validation_failed422Field-level failures in fields[]
amount_below_minimum422Below the live corridor floor
quote_expired422Create a fresh quote
unsupported_corridor422No active corridor for that currency
customer_exists409Duplicate email within your org and mode
account_exists409Customer already has an account in that currency
beneficiary_not_owned403Beneficiary belongs to a different customer
quote_beneficiary_mismatch422Quote and beneficiary currencies differ
idempotency_in_progress409Original request still processing
test_mode_only403Simulation endpoint called with a live key
upstream_validation422The banking provider rejected a field value
upstream_error502Upstream provider fault; safe to retry
route_not_found404Unknown path
internal_error500Unexpected fault; report with your Request-Id

Next steps

Test mode

Real sandboxes at banking providers are slow, gated and periodically wiped. The simulator exists so your CI and local development never depend on any of that, while behaving exactly like the live API: same objects, same events, same async provisioning.

EndpointSimulates
POST /v1/test/customers/{id}/kycKYC outcome: approved, rejected, resubmission_required
POST /v1/test/accounts/{id}/depositsInbound fiat deposit with sender name
POST /v1/test/transfers/{id}Terminal outcome: completed, failed, returned

Simulation endpoints only accept test keys. Calling them with a live key returns test_mode_only.

Examples

Every simulation emits the same events as the real transition, so your webhook handling is exercised end to end. Test data is isolated per organization and never touches live records.

Next steps

Endpoint reference

Customers

POST/customersCreate a customer
GET/customers · /customers/{id}List / retrieve
POST/customers/{id}/kyc_sessionHosted verification URL
POST/customers/{id}/wallet_sessions · /verifyClient-signed wallet sessions
GET/jurisdictions/{cc}/profile_schemaPer-country profile fields Public
PATCH/customers/{id}Update profile ("tell us about yourself")

Accounts & beneficiaries

POST/customers/{id}/accountsProvision a virtual account
GET/customers/{id}/accountsList accounts
POST/customers/{id}/beneficiariesCreate a bank destination
GET/customers/{id}/beneficiariesList beneficiaries

Corridors, quotes, transfers

GET/corridorsLive corridor availability + minimums Public
GET/jurisdictionsResidency rules + currency availability Public
GET/corridors/{ccy}/beneficiary_schemaBank form schema Public
POST/quotesDeterministic pricing
POST/transfersExecute (Idempotency-Key required)
GET/transfers · /transfers/{id}List / retrieve with timeline

Events & webhooks

GET/events · /events/{id}Durable 90-day event log
POST/webhook_endpointsRegister an endpoint (secret shown once)
GET/webhook_endpointsList endpoints
DEL/webhook_endpoints/{id}Delete an endpoint

Test mode

POST/test/customers/{id}/kycForce KYC outcome
POST/test/accounts/{id}/depositsSimulate deposit
POST/test/transfers/{id}Force transfer outcome
Moneva Platform · API v1 draft · Sandbox live, self-serve

Build your flow

EUR deposit BRL Pix USDC NGN payout GBP deposit EUR SEPA Any crypto BRL Pix Deposit only
Choose a source and a destination to assemble the flow.
Source
Destination

Playground

Select flow
DepositFund via bank transfer
PayoutBank payout abroad
CardsIn development
P2PIn development
Crypto inAny token → USDC · In development
Customer residency
Payout corridor
All Bank Instant Mobile Wallet
Configure auth
Passkey
Email
Google
Apple
Notifications
Branded emails · ACME Pay
White-label status emails to the end user, sent by the platform in your brand. Toggling calls PATCH /v1/notification_settings; each send appears as a notification.email.sent event.
9:41
API calls

API console

Every documented request is runnable. Pick an endpoint, adjust the body, and send it against the sandbox: real gateway, real status codes, real payloads. Use your mk_test_ key; keep live keys server-side.

POSThttps://platform.moneva.io
Path
Replace {placeholders} with real ids from previous responses.
Body params
Credentials
Header
Base URL
Test keys only; stored in this browser. Public reference endpoints need no key.
Request

        
Response
Try it to see the live response here.
Search the docs…⌘K