/api/v1/payments/verifyManually verify a Stripe Payment Intent and fulfill the top-up if it succeeded.
Manually verify a Stripe Payment Intent and fulfill the top-up if it succeeded. EXEMPT (`cost: 0`). Webhook-fallback path — use when /payments/webhook delivery was delayed or lost. Idempotent: a Transaction already linked to the intent is treated as already-processed.
Why use this
Common use case
Webhook-fallback fulfilment path for one-time USD top-ups. EXEMPT (cost: 0). Idempotent: a Transaction row already keyed to the payment_intent_id short-circuits the call (returns success without re-crediting). Wraps the same _fulfill_topup helper as /api/v1/payments/webhook, so a verify-fulfilled top-up is indistinguishable from a webhook-fulfilled one (same row in transactions, same credit_balance increment). Use this only as a fallback — the webhook is the canonical path; verify is for the case where the webhook didn't fire within ~30 seconds. Security: server validates that the intent's metadata.user_uuid matches the authenticated user before fulfilling — pasting another user's payment_intent_id returns 500 with Payment Intent does not belong to user. To create the intent in the first place use POST /api/v1/payments/create-intent; to spend the credited balance on quota use POST /api/v1/billing/quota; to inspect the live wallet balance use GET /api/v1/user/ (credit_balance field).
Parameters
| Name | In | Required | Default | Allowed | Description | Example |
|---|---|---|---|---|---|---|
| payment_intent_id | body | required | — | — | Stripe Payment Intent ID returned from POST /payments/create-intent (the `pi_XXXXXX` portion of the `clientSecret`). Must belong to the authenticated user — server cross-checks against the intent's `metadata.user_uuid` and returns 500 with `Payment Intent does not belong to user` on mismatch. | pi_3OqK2jK9L8pQ4xZ1 |
Response schema
| Field | Type | Nullable | Description |
|---|---|---|---|
| status | string | no | ApiResponse envelope status — `success` on 200, `error` on 4xx/5xx. |
| request_id | string | yes | Per-request correlation ID. Pass in support tickets. |
| timestamp | string | no | ISO-8601 UTC timestamp the response was generated. |
| data | object | no | Payload — verification result + post-fulfilment balance. |
| data.status | string | no | `verified` when the intent was found, security-checked, and (if not yet processed) fulfilled. Returns 400 with `Payment not successful or pending` if the Stripe intent's status is not `succeeded` (e.g. still `processing` or `requires_action`). |
| data.balance | number | no | Current `users.credit_balance` in USD AFTER any newly-applied top-up (or unchanged if the verify was a no-op due to prior fulfilment). Use to update the UI balance chip without a separate /account/balance call. |
Sample response
- "status": "success"
- "request_id": "req_3OqK2jK9L8pQ4xZ2"
- "timestamp": "2026-05-02T15:51:00.000Z"
- "data":
- "status": "verified"
- "balance": 41.5
Errors
| Status | Label | Description |
|---|---|---|
| 200 | OK | Request succeeded. |
| 400 | Bad Request | Invalid query, body, or path parameter. |
| 401 | Unauthorized | Missing or invalid Authorization header / api_Token. |
| 402 | Payment Required | Insufficient token balance for this call. Top up |
| 429 | Too Many Requests | Rate limit exceeded for your tier (see /pricing for tier limits). Tier limits |
| 500 | Server Error | Unexpected server-side failure. Retry with backoff; report if persistent. |
Code samples
curl -X POST "https://api.finradar.ai/api/v1/payments/verify" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Generate an API key in /account/credentials to run live queries (literal YOUR_API_KEY placeholder shown until then).