Accept payments
Payments
Accept one-time payments, authorize and capture, and handle refunds.
Overview
The Payment object represents a single charge against a customer. PaymentHooks routes the payment through your configured gateway based on routing rules (currency, amount, region, or gateway health). Each payment goes through a lifecycle of statuses from creation to settlement. Every object also carries a livemode flag — true for live events, false for test-mode events.
PaymentIntents vs Charges
PaymentHooks follows the Stripe two-object model for one-time card payments:
- PaymentIntent (
pi_…) — the intent to collect money. Created viaPOST /v1/payments. Has theamount,currency,customer, andcaptureMethod. A PaymentIntent can produce 0, 1, or many charges. - Charge (
ch_…) — the actual money movement with the gateway. Created by the routing engine after the intent succeeds. Carries thegateway_transaction_id,card_last4,refunded_amount, and therefundslist.
The response from POST /v1/payments is a PaymentIntent with an embedded latest_charge reference once a charge has been created. Refunds happen against a Charge, not a PaymentIntent — but the POST /v1/payments/:id/refund endpoint accepts a PaymentIntent id and routes to the right charge.
Create a payment
curl -X POST https://paymenthooks.com/api/v1/payments \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_test_..." \
-d '{"amount": 5000, "customerId": "cus_abc123", "currency": "usd", "description": "Order #1234"}'Parameters
| Field | Type | Description |
|---|---|---|
amount | integer | Amount in the smallest currency unit (cents for USD). Must be positive. |
customerId | string | ID of the customer to charge. |
currency | stringDefault: | Three-letter ISO currency code (e.g. usd, eur). Defaults to USD. |
gatewayId | string | Force a specific gateway (overrides routing rules). |
paymentMethod | string | Payment method type (e.g. card, bank_transfer). |
captureMethod | stringDefault: | Either automatic (default — capture immediately on auth) or manual (authorize now, capture later via /capture). Useful for pre-auth holds and two-step checkout. |
description | string | Description for the payment (max 500 chars). |
metadata | object | Key-value string pairs for your internal reference. |
idempotencyKey | string | Unique key to prevent duplicate payments (max 255 chars). |
Endpoints & Responses
Complete payload and response examples for every payments endpoint. Click copy to grab the JSON.
/v1/paymentsCreate a new payment{
"amount": 5000,
"customerId": "cus_abc123",
"currency": "usd",
"description": "Order #1234",
"metadata": { "orderId": "1234" },
"idempotencyKey": "idem_order1234_v1"
}{
"data": {
"id": "pay_9k2m4n6p8r0t",
"amount": 5000,
"currency": "usd",
"status": "pending",
"customerId": "cus_abc123",
"gatewayId": "gw_stripe_01",
"description": "Order #1234",
"metadata": { "orderId": "1234" },
"paymentMethod": null,
"merchantId": "mer_abc123",
"createdAt": "2026-05-16T10:30:00.000Z",
"updatedAt": "2026-05-16T10:30:00.000Z"
},
"error": null
}/v1/payments?page=1&pageSize=10&status=succeededList all payments{
"data": [
{
"id": "pay_9k2m4n6p8r0t",
"amount": 5000,
"currency": "usd",
"status": "succeeded",
"customerId": "cus_abc123",
"gatewayId": "gw_stripe_01",
"description": "Order #1234",
"createdAt": "2026-05-16T10:30:00.000Z"
}
],
"meta": { "total": 42, "page": 1, "pageSize": 10 },
"error": null
}/v1/payments/:idRetrieve a payment{
"data": {
"id": "pay_9k2m4n6p8r0t",
"amount": 5000,
"currency": "usd",
"status": "succeeded",
"customerId": "cus_abc123",
"gatewayId": "gw_stripe_01",
"description": "Order #1234",
"metadata": { "orderId": "1234" },
"paymentMethod": "card",
"merchantId": "mer_abc123",
"gatewayTransactionId": "ch_3Nq2x...",
"createdAt": "2026-05-16T10:30:00.000Z",
"updatedAt": "2026-05-16T10:30:05.000Z"
},
"error": null
}/v1/payments/:idUpdate a payment{
"description": "Updated order description",
"metadata": { "orderId": "1234", "note": "VIP customer" }
}{
"data": {
"id": "pay_9k2m4n6p8r0t",
"amount": 5000,
"currency": "usd",
"status": "succeeded",
"customerId": "cus_abc123",
"description": "Updated order description",
"metadata": { "orderId": "1234", "note": "VIP customer" },
"updatedAt": "2026-05-16T11:00:00.000Z"
},
"error": null
}/v1/payments/:id/refundRefund a payment{
"amount": 2500,
"reason": "Customer requested partial refund"
}{
"data": {
"id": "pay_9k2m4n6p8r0t",
"amount": 5000,
"currency": "usd",
"status": "partially_refunded",
"refundedAmount": 2500,
"refunds": [
{
"id": "ref_abc123",
"amount": 2500,
"reason": "Customer requested partial refund",
"createdAt": "2026-05-16T12:00:00.000Z"
}
],
"updatedAt": "2026-05-16T12:00:00.000Z"
},
"error": null
}Manual capture & refund
Pass captureMethod: "manual" on create to authorize without capturing. The payment lands in requires_capture; capture it later with the /capture endpoint. Useful for pre-auth holds, marketplace escrow flows, or two-step checkout where fulfillment precedes the charge.
curl -X POST https://api.paymenthooks.com/v1/payments \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"amount": 7500,"customerId":"cus_abc123","currency":"usd","captureMethod":"manual"}'{
"data": {
"id": "pay_9k2m4n6p8r0t",
"amount": 7500,
"currency": "usd",
"status": "requires_capture",
"captureMethod": "manual",
"authorizedAmount": 7500,
"customerId": "cus_abc123",
"createdAt": "2026-05-16T10:30:00.000Z"
},
"error": null
}curl -X POST https://api.paymenthooks.com/v1/payments/pay_9k2m4n6p8r0t/capture \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"amount": 5000}' # omit for full captureRefunds operate against the underlying charge regardless of capture state. See POST /v1/payments/:id/refund in the endpoints table above; refunds for a partial amount transition the payment to partially_refunded, full refunds transition it to refunded.
3D Secure (step-up authentication)
When a card issuer's 3DS rules require customer verification, the create response carries nextAction: { type: "use_stripe_sdk", challengeUrl: "…" } and the payment status moves to requires_action. You must complete the challenge on the customer's device before the charge finalizes.
- Create the payment. If
status === "requires_action", the response includes anextAction.challengeUrland aclientSecret. - Render the challenge URL (typically inside our Hosted Checkout or your own iframe) on the customer's device. They complete OTP / biometric / passkey authentication.
- Poll
GET /v1/payments/:id/3ds— it returns the recordedstatus(authenticated/challenge_required/failed/exempted), the 3DS version, ECI, and theauthenticationValue(CAVV / AAV). - The payment transitions automatically:
payment.succeededon success,payment.failedon failure. Webhooks are still your source of truth — don't poll the charge endpoint; wait for the event.
curl -X POST https://api.paymenthooks.com/v1/payments/pay_9k2m4n6p8r0t/3ds \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"cardBin":"424242","customerEmail":"[email protected]","customerCountry":"US","returnUrl":"https://yoursite.com/payments/complete"}'{
"data": {
"status": "challenge_required",
"version": "2.2",
"challengeUrl": "https://acs.example.com/3ds/challenge?token=…",
"authenticationValue": null,
"dsTransId": null,
"eci": null,
"exemption": null
},
"error": null
}Payment statuses
pendingPayment created but not yet processedprocessingPayment is being processed by the gatewayrequires_actionAwaiting 3D Secure or another step-up challenge. See the 3D Secure section.requires_captureAuthorized but not captured (captureMethod=manual). See the capture section.succeededPayment completed successfullyfailedPayment was declined or failedrefundedFull refund issuedpartially_refundedPartial refund issued