Skip to content
/api/v1/payments/verify

Manually 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

Manual fallback for the webhook fulfilment path. EXEMPT (`cost: 0`). Calls `stripe.PaymentIntent.retrieve(payment_intent_id)` server-side; if the intent's `metadata.user_uuid` matches the authenticated user (security check) AND the status is `succeeded` AND no `Transaction` row already references this intent (idempotency), then `_fulfill_topup` runs to credit the wallet — same code path as the webhook handler, so the result is identical. Use this when Stripe webhook delivery was delayed or lost (network blip, infrastructure outage, webhook secret mismatch); the frontend can poll this endpoint for ~30 seconds after `confirmCardPayment` resolves to give the webhook a chance to fire first, then fall back to verify.

Common use case

Frontend top-up flow: after `stripe.confirmCardPayment` resolves with `paymentIntent.status === 'succeeded'`, poll /api/v1/account/balance every 2 seconds for up to 30 seconds to see if the webhook credited the wallet. If still not credited, call POST /payments/verify to force-credit. UI shows 'Confirming top-up...' during the poll.

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

NameInRequiredDefaultAllowedDescriptionExample
payment_intent_idbodyrequiredStripe 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

FieldTypeNullableDescription
statusstringnoApiResponse envelope status — `success` on 200, `error` on 4xx/5xx.
request_idstringyesPer-request correlation ID. Pass in support tickets.
timestampstringnoISO-8601 UTC timestamp the response was generated.
dataobjectnoPayload — verification result + post-fulfilment balance.
data.statusstringno`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.balancenumbernoCurrent `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

StatusLabelDescription
200OKRequest succeeded.
400Bad RequestInvalid query, body, or path parameter.
401UnauthorizedMissing or invalid Authorization header / api_Token.
402Payment RequiredInsufficient token balance for this call. Top up
429Too Many RequestsRate limit exceeded for your tier (see /pricing for tier limits). Tier limits
500Server ErrorUnexpected 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).