Skip to content

Pay an invoice by card

The invoices.hosted.pay operation: POST /api/v1/public/invoices/{invoiceId}/pay in the REST API and the invoices_hosted_pay tool in the MCP server.

POST/api/v1/public/invoices/{invoiceId}/pay

Starts a card payment of an open invoice's amount due at the brand's processor: a one-off payment (no card is saved), one payment intent per invoice and amount. expectedAmount is the amount due the customer saw; if it changed meanwhile, the payment is refused.

  • fake processor: the payment is confirmed here with the chosen test card (paymentMethod): succeeded, processing, requires_action (confirm again to pass the fake 3-D Secure challenge) or a decline;
  • Stripe: the answer is client_confirm with the Payment Element's clientSecret; the browser confirms;
  • Mollie: the answer is redirect with redirectUrl, Mollie's payment page; the customer comes back to this page (processor_return=1), which waits for the webhook. A payment that failed or expired on Mollie's page is replaced by a new one when the customer pays again.

The invoice is marked paid when the processor's webhook confirms the payment (invoice.paid; a past-due or unpaid subscription recovers): read the invoice again with invoices.hosted.get. A declined card on this page doesn't count as a dunning attempt. Public: the page calls it with the invoice link's token.

At a glance

  • REST: POST /api/v1/public/invoices/{invoiceId}/pay
  • MCP tool: invoices_hosted_pay
  • Operation ID: invoices.hosted.pay
  • Scope: Brand
  • Permission: invoices:write
  • Risk tier: financial
  • Dry run: Supported (send Ownrecord-Dry-Run: true)
  • Idempotency: Idempotency-Key required
  • Modes: Test and live
  • Authentication: None (a public endpoint, rate-limited per IP address)
  • Since: M5

Request

Path parameters

FieldTypeDescription
invoiceIdinv_… ID

Body

A JSON object (Content-Type: application/json):

FieldTypeRequiredDescription
tokenstringNoThe invoice link's token (needed without an API key).
expectedAmountobjectYesThe amount due the customer saw; a different amount due refuses the payment.
expectedAmount.amountstringYesInteger micros as a string.
expectedAmount.currencystringYes
paymentMethodstringNoFake processor only: the test card, e.g. pm_fake_visa.

Response

200 OK with a JSON object:

FieldTypeDescription
object"hosted_invoice_payment"
invoiceIdinv_… ID
outcomeone of succeeded, processing, requires_action, client_confirm, redirectsucceeded (the invoice is marked paid when the processor's webhook arrives), processing, requires_action (confirm again: a fake 3-D Secure challenge), client_confirm (Stripe: confirm in the Payment Element with clientSecret) or redirect (Mollie: send the buyer to redirectUrl; they come back to the invoice page).
amountmoney
clientSecretstring or null
publishableKeystring or null
redirectUrlstring or null

Examples

Pay with a fake test card

HTTP
POST /api/v1/public/invoices/inv_01m4tx5nj8emwbyf7y9vcf47zn/pay
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "token": "WyJvcmdfMDEiLCJlbnRfMDEiLCJicmRfMDEiLDBd.c2lnbmF0dXJl",
  "expectedAmount": {
    "amount": "1200000000",
    "currency": "EUR"
  },
  "paymentMethod": "pm_fake_visa"
}

Response (200):

JSON
{
  "object": "hosted_invoice_payment",
  "invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47zn",
  "outcome": "succeeded",
  "amount": {
    "amount": "1200000000",
    "currency": "EUR"
  },
  "clientSecret": null,
  "publishableKey": null,
  "redirectUrl": null
}

Errors

CodeHTTP statusWhenWhat to do
validation_failed400A field is malformed or out of range.Fix the fields listed in details.issues and send the request again.
resource_not_found404No issued invoice has this ID in the brand, or the link's token doesn't belong to it.Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.
invoice_payment_unavailable409The invoice is paid or voided, has nothing due, its amount due changed, or the brand has no processor.The invoice is paid, voided or has nothing due, or the brand has no processor connection: check details.reason. Pay by bank transfer with the details on the invoice, or ask the seller.
processor_payment_declined502The card was declined; try another card or a bank transfer.Ask the customer for another payment method; details.decline_code says why the card was declined.
processor_request_failed502The processor couldn't create or confirm the payment; try again.If retryable is true, retry with the same Idempotency-Key after a short wait; otherwise check details.code.

It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, scope_required, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, rate_limited, internal_error.