Source-of-funds (SoF) verification proves that the money a user is moving came from a legitimate source. Geldstuck supports 10+ document types - bank statements, tax returns, salary slips, sale-of-asset receipts, and more - all under a single API.

Supported document types

TypeTypical evidence
bank_statementLast 3 months, issuing bank visible
salaryPayslip or employment letter
tax_returnAnnual filing
sale_of_propertyDeed or sale contract
sale_of_assetInvoice + receipt
inheritanceExecutor statement, probate docs
giftDonor declaration + source proof
loanLoan agreement + disbursement proof
investment_incomeBroker statement
business_incomeFinancial statements

Lifecycle

1

Create the SoF record

One record per user. It’s a container for multiple document groups.
2

Upload documents

Each document group is keyed by type (e.g., bank_statement) and contains file URLs + metadata.
3

Submit for review

Submitting locks the record and notifies a reviewer. You’ll get a source_of_funds.reviewed webhook when done.

Step 1 - Create the record

curl https://api.geldstuck.com/v1/source-of-funds \
  -H "x-api-key: $GELDSTUCK_PUBLIC_KEY" \
  -H "x-api-secret: $GELDSTUCK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "userId": "usr_01HX3ZAB..." }'

Step 2 - Upload documents

Files are uploaded with a temporary upload URL - you don’t send file bytes to the source-of-funds endpoint itself.
1

Request an upload URL

POST /v1/files/upload-url
{
  "filename": "chase-statement-2026-03.pdf",
  "contentType": "application/pdf",
  "purpose": "source_of_funds"
}
Response:
{ "fileId": "file_01HX3ZK...", "uploadUrl": "https://..." }
2

PUT the file bytes to `uploadUrl`

curl -X PUT --upload-file chase-statement-2026-03.pdf "$UPLOAD_URL"
3

Attach the file to the SoF record

PUT /v1/source-of-funds/usr_01HX3ZAB...
{
  "bankStatement": {
    "institution": "Chase",
    "accountLast4": "1234",
    "fileIds": ["file_01HX3ZK..."]
  }
}
Updates use a deep $set - you can PATCH individual document groups without clobbering siblings.

Step 3 - Submit for review

curl https://api.geldstuck.com/v1/source-of-funds/submission \
  -H "x-api-key: $GELDSTUCK_PUBLIC_KEY" \
  -H "x-api-secret: $GELDSTUCK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "userId": "usr_01HX3ZAB..." }'
Submission flips the status to under_review. You’ll receive a webhook once a reviewer rules:
{
  "id": "evt_01HX3ZL...",
  "type": "source_of_funds.reviewed",
  "tenantId": "tnt_01HX3Z8MQW...",
  "data": {
    "userId": "usr_01HX3ZAB...",
    "decision": "approved",
    "reviewedAt": "2026-04-22T15:02:11.000Z",
    "reviewerNotes": "Bank statements match declared salary."
  }
}

Decisions

decisionMeaningNext step
approvedUser may proceed.Unblock transactions.
revisions_requiredReviewer wants more/better docs. reviewerNotes explains.Prompt user to re-upload.
rejectedDocuments insufficient or suspicious.Block further activity and surface the reason.
Store the SoF decision timestamp on your user record. Many regulators require re-verification after 12–24 months - you can use the timestamp to schedule refresh prompts.