Skip to Content
ChargesIntegration Guide

Integration Guide

This guide walks through a first payment with the Charges API, from creating the charge to knowing that the payment succeeded.

Follow it against the Playground environment first. Playground accepts the same calls as production but does not move real money, and the Test Data page has cards that produce each outcome.

Before you start, you need:

  • Your Playground merchant ID and API key. See Authentication.
  • A webhook endpoint that Beam can reach over HTTPS. See the Webhook guide.
Tip

The Postman collection has every request in this guide, ready to run, if you would rather not type them yourself.

Step 1: Create the charge

Call POST /api/v1/charges from your backend with the amount, the currency, and the payment method. The amount is in the smallest currency unit, so 300099 means 3,000.99 THB.

POST https://playground.api.beamcheckout.com/api/v1/charges Content-Type: application/json Authorization: Basic <base64(merchantId:apiKey)> { "amount": 300099, "currency": "THB", "paymentMethod": { "paymentMethodType": "CARD", "card": { "cardHolderName": "Cardholder Name", "expiryMonth": 12, "expiryYear": 30, "pan": "4111111111111111", "securityCode": "123" } }, "referenceId": "TestReferenceId", "returnUrl": "https://www.beamcheckout.com" }

Beam replies with the chargeId and an actionRequired field:

{ "actionRequired": "NONE", "chargeId": "ch_2xTsz7Qit55pahSvKfJG3UMkpFQ", "paymentMethodType": "CARD" }

Store the chargeId against your order. You need it to look the charge up later, and to refund it.

Important

A 201 response means the charge was created, not that the payment succeeded. Do not fulfill the order yet.

Tip

Send an idempotency key with this call. If your request times out and you retry, the key stops Beam creating a second charge for the same order.

Step 2: Finish the shopper’s part of the payment

What the shopper does next depends on actionRequired. Handle all three values, because the value changes with the payment method.

REDIRECT: send the shopper to redirect.redirectUrl. They complete the payment there (a 3DS challenge, a bank app, or a wallet), then come back to the returnUrl you sent in step 1.

{ "actionRequired": "REDIRECT", "chargeId": "ch_2xTsz7Qit55pahSvKfJG3UMkpFQ", "redirect": { "redirectUrl": "https://www.beamcheckout.com/redirect" }, "paymentMethodType": "CARD" }

ENCODED_IMAGE: decode encodedImage.imageBase64Encoded and show the QR code to the shopper, who scans it in their banking or wallet app. Show the expiry too, so the shopper knows how long the code is valid.

{ "actionRequired": "ENCODED_IMAGE", "chargeId": "ch_2xTsz7Qit55pahSvKfJG3UMkpFQ", "encodedImage": { "expiry": "2025-08-24T14:15:22Z", "imageBase64Encoded": "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAAB...", "rawData": "Sample QR Code for payment" }, "paymentMethodType": "QR_PROMPT_PAY" }

NONE: nothing more is needed from the shopper. Show them that the payment is being processed and move on to step 3.

Step 3: Wait for the result

Beam sends a webhook when the charge reaches a final status. Subscribe to charge.succeeded and charge.failed, and fulfill the order when charge.succeeded arrives. This is the reliable signal: it reaches your backend even if the shopper closes their browser right after paying.

Your endpoint should verify the signature, respond with HTTP 2xx quickly, and do the slow work afterwards. See Authenticating Webhooks and Event Types for the payloads.

If you cannot receive webhooks yet, poll the charge instead:

GET https://playground.api.beamcheckout.com/api/v1/charges/{chargeId} Authorization: Basic <base64(merchantId:apiKey)>

Keep polling until status is SUCCEEDED or FAILED. Use polling only until your webhook endpoint is ready, and move to webhooks before you go live. Poll with exponential backoff and jitter, because polling too fast gets 429 responses. See Rate limits and retries.

Either way, give the charge your own timeout. A shopper who abandons the payment can leave it PENDING for good, so decide how long you will wait, then treat the order as unpaid and release whatever you were holding. See Not every charge reaches a final status.

Step 4: Handle a failed charge

When status is FAILED, the failureCode says why. Map it to a message for the shopper, using the charge failure codes table.

{ "chargeId": "ch_2xTsz7Qit55pahSvKfJG3UMkpFQ", "status": "FAILED", "failureCode": "CH_CARD_DECLINED", "amount": 300099, "currency": "THB", "referenceId": "TestReferenceId" }

A failed charge cannot be retried. To let the shopper try again, create a new charge. The Test Data page lists the cards that produce each failure code, so you can test these paths before you go live.

Next steps

Last updated on