Collect payment
Payment is not a step at the end of the journey. On most products it sits between confirmation and signature, because the policyholder signs a direct debit mandate at the same moment they sign the contract — and on some products cover does not begin until the first premium has been collected.
Getting the ordering wrong is the most common way an otherwise correct integration stalls: the signature request is rejected, and the error names a payment method rather than the signature.
Korint uses Stripe as its payment processor. We hold the Stripe account; you never call Stripe's API server-side. Your only Stripe interaction is confirming a setup in the customer's browser with a client secret we give you.
What the product decides
Read these from GET /config/{productId} before writing any payment code:
payment.allowedPaymentModesarrayDIRECT_DEBIT— Korint collects from a stored payment method.BANK_TRANSFER— the customer transfers to an account you obtain fromGET /customers/{customerId}/bank-transfer-account, and no payment method is collected.OFFLINE— nothing is collected through the platform: invoices stayISSUEDuntil a broker marks them settled manually.
payment.allowedPaymentMethodsarraysepa_debit,card, or both. These are Stripe payment method types, and they determine what the setup can accept.
payment.paymentMethodOnEarlyPayment / paymentMethodOnRecurringPaymentarrayWhich method types may be charged for the first premium and for recurring premiums. A product can accept a card for the first payment but require a SEPA mandate for the recurring ones.
payment.paymentMethodOnBrokenMethodarrayFallback types charged when the normal method is unusable — a dead mandate, for example. Absent means such invoices are left unpaid.
signature.requirePaymentMethodForNewBusinessbooleanWhen true, a new-business contract cannot be sent for signature until a payment method exists for every direct-debit payer.
requireFirstPaymentToActivatebooleanA top-level property of the product config, not part of
payment. When true, a signed contract only comes into force once the first premium has been collected, in addition to reaching its start date. Optional, and absent on all of the demo products — treat a missing value as false.
payment.paymentMethodIsOnBrokerageFirmbooleanWhen true, the payment method belongs to the distributing brokerage firm rather than the customer, and you collect it on the firm's endpoints instead.
Where payment sits in the journey
Collecting a payment method
- Put the payer on direct debit
A payment method can be stored whenever the product's
allowedPaymentModesincludesDIRECT_DEBIT— whatever the customer's current mode. That is also how anOFFLINEcustomer prepares the switch back to direct debit, which requires a usable method already on file.A new customer already has one: the mode defaults to the first automatic entry of the product's
allowedPaymentModes, whatever the list order — a mixed list never makesOFFLINEa default by accident. The one exception is a product whoseallowedPaymentModesis["OFFLINE"]alone: a full-offline product, whose customers are bornOFFLINEand are invoiced without ever being charged. So on a direct-debit-first product this call is a no-op and you can skip it — but check the config rather than assuming, because on a product that listsBANK_TRANSFERfirst the default is bank transfer and this call is required.Switching back to
DIRECT_DEBITfromOFFLINErequires the customer to already hold a usable payment method — the call fails with400 PAYMENT_METHOD_REQUIREDotherwise — and invoices issued while the customer was offline are not charged retroactively.OFFLINEitself is only accepted on policies billed to a single payer.curl -X PUT 'https://api.sandbox.korint.io/customers/<customerId>/payment-mode' \--header 'Authorization: Bearer <access_token>' \--header 'tenant: <tenant>' \--header 'Content-Type: application/json' \--data '{ "paymentMode": "DIRECT_DEBIT" }'Asking for a setup secret on a product whose
allowedPaymentModesexcludesDIRECT_DEBITfails withCONFLICTING_CONFIGnamingallowedPaymentModes. - Get a client secretcurl 'https://api.sandbox.korint.io/customers/<customerId>/payment-methods/secret' \--header 'Authorization: Bearer <access_token>' \--header 'tenant: <tenant>'{ "clientSecret": "seti_<id>_secret_<secret>" }
This is a Stripe SetupIntent client secret, created on our Stripe account and restricted to the product's
allowedPaymentMethods. It is safe to pass to the browser; it authorises setting up one payment method for one customer and nothing else.allowedPaymentMethodscan be wider than what the contract can actually charge: the SetupIntent accepts everything in that list, while recurring premiums are limited topaymentMethodOnRecurringPayment. Render the intersection of the two — pluspaymentMethodOnEarlyPaymentwhen the first payment is an early one — rather than whatever the SetupIntent permits, or Stripe's Payment Element will offer a card tab that stores a method the contract can never charge.The response does not include a publishable key, and no endpoint serves one. Stripe.js must be initialised with the publishable key of our Stripe account for your tenant and environment — ask Korint for it.
Quote the policy before asking for a secret. The customer's counterpart on the payment side is created when the policy is quoted, not when the customer is created. Ask earlier in the journey — after creating the customer, after setting the payment mode, even after
calculate-quote— and you get:404 PAYMENT_CUSTOMER_NOT_FOUNDPOST /policies/{policyId}/quoteis what creates it;calculate-quotedoes not, because it persists nothing. Confirmation creates it too, for contracts that reach that point another way. - Confirm the setup in the browser
Use Stripe.js with the client secret. For a SEPA direct debit the customer enters an IBAN; for a card, card details. Follow Stripe's own guides — we deliberately do not restate them here:
- Stripe SetupIntents
The setup flow the client secret belongs to.
- Stripe Elements
Collecting IBAN or card details in the browser.
We learn the result from Stripe directly, by webhook — you do not report it back to us. The payment method appears on
GET /customers/{customerId}/payment-methodsshortly after the setup succeeds, which makes this an asynchronous step: see Operations that settle after the response. - Stripe SetupIntents
- Have the mandate signed, for SEPA
A SEPA direct debit needs a signed mandate authorising the collections. When the product configures one, request it for the stored method:
curl -X POST 'https://api.sandbox.korint.io/customers/<customerId>/payment-methods/<paymentMethodId>/signature' \--header 'Authorization: Bearer <access_token>' \--header 'tenant: <tenant>'Payment methods carry one of four statuses:
ACTIVE— usable; a mandate, if required, has been signed.WAITING_MANDATE_SIGNATURE— stored, mandate not yet signed. Accepted for new business, which is what lets the customer sign the contract and the mandate in one session.INVALID_MANDATE— no longer valid; collections will fail.EXPIRED— the underlying method has expired, typically a lapsed card.
Set the method the customer should be charged on with
PUT /customers/{customerId}/payment-methods/{paymentMethodId}/preferredwhen more than one exists.
When the first premium is taken
invoicingConfig.firstPayment decides this, and it is a value you set on the policy:
NONE— no distinct first payment; the contract is billed on its normal schedule.FULL_AT_SIGNATURE/EARLY_PAYMENT_AT_SIGNATURE— charged when the contract is signed, either the full first premium or an early-payment portion.FULL_AT_START_DATE/EARLY_PAYMENT_AT_START_DATE— charged when cover begins.
If the product also sets requireFirstPaymentToActivate, cover does not begin until that payment has
been collected. A contract that is signed, with a start date in the past, and still not in force is
usually waiting on exactly this — not on a failure. isAwaitingFirstPayment on the policy tells you so;
read the invoices from GET /customers/{customerId}/invoices.
POST /customers/{customerId}/retry-charge re-attempts a failed collection, and
GET /customers/{customerId}/balance shows what is outstanding. For what happens when payments keep
failing, see Manage unpaid invoices.
Source of NO_VALID_PAYMENT_METHODS on a signature request. POST /policies/{policyId}/signature
is refused when the branch is new business, the payer is on DIRECT_DEBIT, the product sets
signature.requirePaymentMethodForNewBusiness, and no payment method for that payer is ACTIVE or
WAITING_MANDATE_SIGNATURE.
An early-payment firstPayment adds a second condition that is easy to miss: the payer also needs a
method whose type appears in payment.paymentMethodOnEarlyPayment, so a stored SEPA mandate does not
satisfy a product that takes the first payment by card.
The ordering to implement is confirm → payment mode → client secret → browser setup → mandate request → signature.