Authorize first, capture later (J5)

Guide

Some orders can't be charged at the moment the customer clicks pay - you might still need to confirm stock, weigh the goods, or hand-finalize the price. The two-step authorize-then-capture flow (sometimes called "J5", after Grow's authorization-only flag) lets you place a hold on the customer's card now and move the money later, once you're sure you can fulfil. This guide walks the flow end-to-end using a hosted checkout session.

When to use it

  • Variable-weight goods (butcher, fishmonger, deli) where the final amount is computed after picking.
  • Made-to-order or small-batch storefronts where stock is confirmed manually before shipping.
  • Marketplaces or galleries where a human approves each order before charging.
  • Pre-orders or waitlists where you want a card on file but won't bill until the item ships.

The flow at a glance

  1. Create a checkout session with capture_method: "manual".
  2. Redirect the customer to the hosted page. They enter card details and submit.
  3. CHING authorizes the card via Grow (J5 hold), redirects the customer to your success_url, and fires a charge.authorized webhook to your registered endpoint.
  4. You receive the charge id from the webhook payload. This is the only place the id appears for a hosted-checkout manual charge - the redirect URL does not carry it.
  5. Once stock is confirmed, call POST /v1/charges/{id}/capture to move the money. Or call POST /v1/charges/{id}/cancel to release the hold.
  6. If you do nothing within 7 days, CHING auto-cancels the hold and fires charge.canceled with cancellation_reason: "expired".

1. Create the manual-capture checkout session

Cart-mode session, set capture_method: "manual":

Terminal
curl -X POST https://api.ching.co.il/ching/v1/checkout_sessions \
  -H "Authorization: Bearer ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "cus_V8ltq1pK_MWH",
    "line_items": [
      { "name": "Ribeye (estimated 600g)", "amount_agorot": 18000, "quantity": 1 }
    ],
    "capture_method": "manual",
    "success_url": "https://shop.example.com/order/thanks",
    "cancel_url":  "https://shop.example.com/cart"
  }'

Price-mode sessions work too - pass a one-time price id instead of line_items.

2. Redirect the customer

The response carries a hosted-checkout url at data.url. Send the customer there. CHING displays a card-entry form (saved cards and express wallets are hidden for manual sessions - see Constraints below), runs Grow's J5 authorization, and redirects to your success_url on success.

The success redirect is informational. It tells you the customer reached the end of the flow, but it does not carry the charge id, and authorization can still fail server-side. Treat the redirect as "show a thank-you page", never as "the order is paid".

3. Receive the charge id via webhook

On successful authorization CHING POSTs a charge.authorized event to every webhook endpoint subscribed to it. The payload mirrors a charge object, with status requires_capture:

{
  "id": "evt_m2n3o4p5q6r7",
  "type": "charge.authorized",
  "data": {
    "id": "ch_9mTPfRSDmEOU",
    "object": "charge",
    "customer": "cus_V8ltq1pK_MWH",
    "amount": 18000,
    "amount_captured": null,
    "currency": "ils",
    "status": "requires_capture",
    "capture_method": "manual",
    "captured": false,
    "authorized_at": "2026-05-17T10:11:12.000Z",
    "capturable_until": "2026-05-24T10:11:12.000Z",
    "checkout_session": "cs_aB3xPQrLm9Tk",
    "line_items": [
      { "name": "Ribeye (estimated 600g)", "amount_agorot": 18000, "quantity": 1 }
    ],
    "livemode": true
  },
  "created": "2026-05-17T10:11:12.000Z"
}

Store the data.id on your order row, keyed by data.checkout_session so you can correlate the webhook back to the cart you originally created. capturable_until is the hard deadline - after that the daily sweep cancels the hold.

Subscribe to charge.authorized explicitly. A webhook endpoint that only subscribes to charge.succeeded will never see manual-capture authorizations and will be stuck without a charge id. Add charge.authorized, charge.captured, and charge.canceledto the endpoint's event list, or use ["*"] to receive every event.

4a. Capture (when you're ready to charge)

Stock confirmed, weight known. Capture for the actual amount. Omit amountto capture the full authorized hold; pass a smaller value for a partial capture and the unused difference is auto-released to the customer's card by Grow.

Terminal
curl -X POST https://api.ching.co.il/ching/v1/charges/ch_9mTPfRSDmEOU/capture \
  -H "Authorization: Bearer ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "amount": 17400 }'

On success CHING fires charge.captured, emails the receipt to the customer, and issues the tax invoice (unless the original session opted out with create_document: false). All three were deferred from the authorization step.

4b. Cancel (when you can't fulfil)

Out of stock, fraud signal, customer changed their mind. Release the hold instead of capturing:

Terminal
curl -X POST https://api.ching.co.il/ching/v1/charges/ch_9mTPfRSDmEOU/cancel \
  -H "Authorization: Bearer ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "cancellation_reason": "requested_by_customer" }'
Bank-side release delay: CHING releases the hold immediately and fires charge.canceled, but the customer's issuing bank may take up to 10 daysto remove the hold from their available balance (Grow's auto-release window). Tell your customer this upfront so cancellation feels reliable.

Webhook handler sketch

A minimal Node.js handler that persists the charge id and decides what to do next from your own business logic:

Node.js
// After verifying the signature (see Webhook Basics)
switch (event.type) {
  case "charge.authorized": {
    // Hosted-checkout manual-capture flow. event.data.id is the only place
    // this id ever appears for hosted-checkout sessions - persist it.
    await db.orders.update({
      checkout_session: event.data.checkout_session,
      charge_id:        event.data.id,
      status:           "awaiting_fulfilment",
      capturable_until: event.data.capturable_until,
    });
    await notifyOpsTeam(event.data.id);
    break;
  }
  case "charge.captured":
    await db.orders.update({
      charge_id: event.data.id,
      status:    "paid",
      captured_amount: event.data.amount_captured,
    });
    break;
  case "charge.canceled":
    await db.orders.update({
      charge_id: event.data.id,
      status:    "canceled",
      reason:    event.data.cancellation_reason,
    });
    break;
}

Constraints

Where manual capture is NOT allowed:
  • Recurring prices - the API rejects manual capture for subscriptions.
  • Saved cards - manual is only valid with a brand-new card entered at the hosted page.
  • Express wallets (Apple Pay, Google Pay, Bit) - the hosted page hides these for manual sessions.
For any of these, fall back to automatic capture and refund if you need to undo the charge.

Where to next