Verified payments
Take payment on your own Razorpay, Cashfree or Stripe account, and move on only once the gateway confirms it.
A payment question comes in two kinds, and they make very different promises.
| Verified checkout | Payment link or UPI QR | |
|---|---|---|
| Where the money goes | Your own gateway account | Wherever the link or UPI ID points |
| What the answer means | The gateway confirmed the payment | The respondent pressed "I've paid" |
verified on the answer | true | always false |
| Needs | Pro plan and a connected account | Nothing |
The rest of this page is about verified checkout. A link or UPI payment still has to be matched against your own statement by hand.
Verified checkout is being switched on for organizations gradually. Until it is on for yours, the builder only offers the link and UPI kinds.
Your account, your money
You connect your own gateway account. Respondents pay you directly: chatform never holds the money, never pays it out, and takes no cut of any payment. Your gateway's own fees are the only ones. Refunds, disputes and payouts all stay in your gateway's dashboard, exactly as they are for any other payment you take.
Connect an account
In the builder, open a form's Integrate tab and find Take payments.
Every gateway connects with one button. You approve chatform on the gateway's own page and come straight back. There is no key to copy or paste.
- Stripe opens Stripe's own page, where you pick the Stripe account to use, or create one if you have none. Payments are charged on that account directly, in any currency Stripe supports, and Stripe pays them out to your bank on your usual schedule.
- Razorpay and Cashfree accounts take payments in rupees (INR) only.
A preview never charges a live account; it offers Simulate payment instead.
Disconnecting removes chatform's access at the gateway and deletes the stored credential. Payments already recorded stay in your results.
Set up the question
Pick Verified checkout as the payment question's method, choose the account, and set the amount:
- Fixed — one price for everyone.
- Variable — read from a form variable, such as a ticket price multiplied by a quantity. Set a minimum and maximum so an unexpected value stops at the checkout rather than reaching the gateway. A link can pick between amounts your rules set, through a hidden field.
Every amount is worked out on the server from the respondent's own answers. Nothing a browser sends can change what is charged.
Sign-in is optional. A respondent can pay without signing in, which is what a donation or a tip jar wants. If you would rather every payment came with a verified name — a ticket, a booking, anything you may need to refund by person — turn on sign-in in Settings → Access, and it applies to the whole form rather than to this question alone. Either way the gateway keeps the payer's own email and card details.
Publishing checks that the account is still connected, and that a Razorpay or Cashfree question charges in INR. A failed check names the question.
What respondents see
- They reach the question and tap Pay.
- The gateway's checkout opens: a Razorpay or Cashfree window over the form, or Stripe's own page.
- The conversation carries on the moment the gateway confirms the payment. There is no "I've paid" button to press.
If they close the tab straight after paying, the payment is still recorded when the gateway tells chatform about it, even if the conversation has already ended. If a payment fails or they cancel, the question stays open with Try again.
Cashfree needs a phone number for its receipt. When nothing before the payment has given one — no phone sign-in, no phone question — the Pay card asks for one first.
A variable amount follows the answers it is worked out from. If a respondent changes one of those answers after paying, the earlier payment no longer counts for the question: they are asked to pay the new amount, and the earlier payment is flagged for you to refund. Changing the answer back makes the earlier payment count again, with nothing charged twice.
In your results
The answer reads back as:
{
"status": "paid",
"method": "gateway",
"verified": true,
"provider": "razorpay",
"paymentRecordId": "rpay_…",
"paymentId": "pay_…",
"amount": 499,
"currency": "INR",
"paidAt": 1788592149473
}A refund made in your gateway adds "refunded": true. A payment confirmed by a
test-mode account adds "testMode": true and reads "Paid · test mode", never
"verified": a form published on a test account accepts test cards, and no money
moves. Exports put four columns beside every payment question:
payment status, amount, currency and the gateway's payment ID.
Two tabs can mean two payments for one question. The second is flagged as a
duplicate, never refunded automatically — refund it from your gateway. A payment
made before the respondent changed the amount is flagged the same way, with
failure_reason set to amount_changed.
To reconcile every checkout attempt, including unfinished and duplicate ones:
curl https://api.chatform.in/v1/forms/$FORM_ID/payments?status=paid \
-H "x-api-key: $CHATFORM_SECRET_KEY"The key needs the response:read scope.
From the API
A verified payment question cannot be answered with a value. Sending one to
POST /v1/responses/{id}/answers or a session message fails with
payment_unverified.
A headless session starts checkout itself:
curl -X POST https://api.chatform.in/v1/sessions/$SESSION_ID/payments \
-H "x-api-key: $CHATFORM_SECRET_KEY" \
-H "content-type: application/json" \
-d '{"ref": "q_payment"}'The reply is { recordId, launch, expiresAt }. A 422 with phone_required
means the gateway needs a phone number for its receipt: send the same request
again with "phone": "+919876543210". A session opened with a *_test_ key
never charges a live account and is refused with live_account_in_test_mode. launch says how to open the
checkout and never carries a secret; the stream sends the same thing as
payment_required. Once the gateway confirms, it sends payment_settled and
then the next question, or payment_failed if the attempt did not go through.
When the respondent comes back from the gateway,
POST /v1/sessions/{id}/payments/{recordId}/confirm asks chatform to check with
the gateway straight away instead of waiting for its webhook.
Consent
Agreeing to terms, a waiver, a code of conduct. Put the wording in `description`. By default the only answer is yes; add decline=true when a refusal has to be a real answer you can route on: an eligibility gate, a policy someone may decline.
Payment accounts
Connect, rename and disconnect the gateway accounts verified payments are paid into.