Quick start
This guide walks through the first payment, end to end: a user on your books, a source to pull from, a payout, then a hold on your ledger.
Payments are funded from deposits your user already holds with you: you host authorize, capture, and release, and Moov calls them as the payment moves. See flow of funds.
You will need a Moov API key (Dashboard → Developers → API keys), your provider ID (your Moov account ID), a public HTTPS URL Moov can POST to, and a test deposit account on your core.
Register ledger URLs and a signing secret #
Give Moov the URLs you will host, then fetch the wrapping key. Moov POSTs to each URL as-is:
| URL | When |
|---|---|
AuthorizationURL | The user sends |
CaptureURL | The recipient claims |
HoldReleaseURL | The payment is canceled or expires |
Add CreditURL later if you collect on requests. How to register the
URLs, the JWE shape, and a decrypt/encrypt example:
funding setup and
encryption.
Issue the secret once. Decode the base64url value to 32 raw bytes and store it. You cannot fetch it again.
curl -X POST "https://api.moov.money/providers/$PROVIDER_ID/signing-secrets" \
-u "$MOOV_API_KEY_ID:$MOOV_API_KEY_SECRET"
Create signing secret.
A first stub can always return approved with a holdReference you
mint. Wire real balances after the round trip works.
Create a user #
Register each sender once, with your id as externalID. Do this
when they sign up, or the first time you offer them send:
curl -X POST "https://api.moov.money/providers/$PROVIDER_ID/participants" \
-u "$MOOV_API_KEY_ID:$MOOV_API_KEY_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data '{
"externalID": "user-abc-123",
"givenName": "Jane",
"familyName": "Smith",
"address": {
"addressLine1": "123 Main St",
"city": "San Francisco",
"stateOrProvince": "CA",
"postalCode": "94105",
"country": "US"
},
"phoneNumbers": ["+15555550101"]
}'
address is required, matching what you already collect for a
recipient. The 201 returns the assigned participantID.
They start pending while a signup check runs, then active or
denied. Only active can send. Poll rather than minting a token
straight away:
curl "https://api.moov.money/providers/$PROVIDER_ID/participants/user-abc-123" \
-u "$MOOV_API_KEY_ID:$MOOV_API_KEY_SECRET"
A repeat create with the same externalID returns 409. Treat that as
already registered.
Create participant,
get participant.
Add a funding source #
Tell Moov which account they can send from. providerSourceID is
your id for that account. You will see it again on authorize as
fundingSource:
curl -X POST "https://api.moov.money/providers/$PROVIDER_ID/participants/user-abc-123/funding-sources" \
-u "$MOOV_API_KEY_ID:$MOOV_API_KEY_SECRET" \
-H "Content-Type: application/json" \
--data '{
"fundingType": "bank_account",
"providerSourceID": "checking-1234",
"displayName": "Premier Checking",
"last4": "1234"
}'
The response includes a fundingSourceID. Creation is idempotent on
(participant, fundingType, providerSourceID), so re-sending an
existing source returns the same id.
Create funding source.
You can skip this call entirely: the create-participant body takes an
optional fundingSources array (up to 10), so the previous step and
this one can be one request. The 201 doesn’t echo them back; list the
participant’s funding sources to confirm.
Send the first payout #
The payment happens in your app, through the SDK. There is no
provider-authenticated payout endpoint: payouts are created against
the sender’s own session. Mint a
sender token
(clientType: device) for the signed-in user and present the
iOS or
Android
SDK. The SDK resolves the funding sources you registered above from
the token, so there is nothing further to pass in. They pick a person
and an amount; the SDK creates the payout. Moov then hits your
authorize URL.
The token is the only part you build. Mint it with your API key:
curl -X POST "https://api.moov.money/provider-participant-token" \
-u "$MOOV_API_KEY_ID:$MOOV_API_KEY_SECRET" \
-H "Content-Type: application/json" \
--data '{ "externalID": "user-abc-123", "clientType": "device" }'
Create sender token.
Hand access_token to the SDK; it creates the payout against that
sender’s session. You never post the payout yourself.
Your signal that it worked is the authorize call landing on your
ledger, in the next step. The recipient gets a claim link good for 10
days. A payment your ledger refuses, or that the fraud check denies, ends
as a denied payout with a reason the SDK surfaces to your app; see
trust and safety.
Watch the ledger #
The payout is what the SDK created. The ledger is what Moov calls you.
- Authorize fires as the payment is created. Decrypt the JWE. Under
moovyou get the payout ID,externalID,fundingSource(yourproviderSourceID), the amount, and afraudScore. Place a hold on that account. Returnapprovedand aholdReferenceyou can look up later, echoingidempotencyKey. Authorize. - Capture fires when the recipient finishes the claim. Moov sends
back your
holdReference. Debit the hold, returnapprovedand acaptureReference. Capture. - Release fires if they cancel or the 10-day window lapses. The hold comes off; nothing is captured. Release.
Inner JWT: iat and exp at the top, Moov fields under moov.
Encryption
has the unwrap. Outcomes, retries, and what to persist:
ledger endpoints
and idempotency.
A hold ends in exactly one of capture or release. If authorize never arrived, the payout did not land on your core.
Next #
- Overview: payments and requests, who does what.
- Sender authentication: the token you hand the SDK.
- Credit: the callback after a request is paid, if you collect that way.
- Program configuration:
set the
shortNamethat appears on the recipient’s statement. - API reference: every field.