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
| Type | Typical evidence |
|---|
bank_statement | Last 3 months, issuing bank visible |
salary | Payslip or employment letter |
tax_return | Annual filing |
sale_of_property | Deed or sale contract |
sale_of_asset | Invoice + receipt |
inheritance | Executor statement, probate docs |
gift | Donor declaration + source proof |
loan | Loan agreement + disbursement proof |
investment_income | Broker statement |
business_income | Financial statements |
Lifecycle
Create the SoF record
One record per user. It’s a container for multiple document groups.
Upload documents
Each document group is keyed by type (e.g., bank_statement) and contains file URLs + metadata.
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.
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://..." }
PUT the file bytes to `uploadUrl`
curl -X PUT --upload-file chase-statement-2026-03.pdf "$UPLOAD_URL"
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
decision | Meaning | Next step |
|---|
approved | User may proceed. | Unblock transactions. |
revisions_required | Reviewer wants more/better docs. reviewerNotes explains. | Prompt user to re-upload. |
rejected | Documents 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.