This guide takes you from a sandbox key to a successful payment, a refund and a verified webhook. It takes about 15 minutes. Run the steps in order in one language; each step uses the ID returned by the one before.

You need:

  • A VNSwitch sandbox account and its secret API key (dashboard → API keys).
  • A webhook URL set on your business profile and its payment response hash key (dashboard → Webhooks). For local testing, use any HTTPS tunnel to your machine.
  • cURL, Node.js 18+, Python 3 with requests, or Java 17+.

Step 1: Create a payment

POST /payments

Create a payment for 150,000 VND. VND has no minor unit, so amount is 150000 ( Amounts and currencies). With confirm: false the payment waits for a payment method. Keep the returned payment_id: you use it in every later step.

If customer_id does not exist yet, VNSwitch creates the customer for you.

Response

{
  "payment_id": "pay_8f2c1d9e0a",
  "status": "requires_payment_method",
  "amount": 150000,
  "currency": "VND",
  "customer_id": "cus_demo_001",
  "client_secret": "pay_8f2c1d9e0a_secret_Xk2pQ9rTz4",
  "description": "Order #1001",
  "created": "2026-11-01T08:30:00.000Z"
}

Step 2: Confirm with a test card

POST /payments/{payment_id}/confirm

Attach the test card and authorise the payment. With capture_method: "automatic" the money is captured straight away, so the status becomes succeeded.

Raw card numbers in server requests are fine in the sandbox. In production, sending card data through your servers puts them in PCI DSS scope. Talk to us about hosted card entry before you go live.

If the status is requires_customer_action, the card needs 3-D Secure: send the customer to the URL in next_action ( Redirects and customer actions).

Response

{
  "payment_id": "pay_8f2c1d9e0a",
  "status": "succeeded",
  "amount": 150000,
  "amount_received": 150000,
  "currency": "VND",
  "payment_method": "card",
  "payment_method_type": "credit",
  "next_action": null
}

Step 3: Check the payment status

GET /payments/{payment_id}

Your server decides whether an order is paid from this call or from a webhook, never from what the browser says. See Payment lifecycle for every status.

Response

{
  "payment_id": "pay_8f2c1d9e0a",
  "status": "succeeded",
  "amount": 150000,
  "amount_received": 150000,
  "currency": "VND"
}

Step 4: Refund part of the payment

POST /refunds

Refund 50,000 VND of the 150,000 VND payment. If you leave out amount, VNSwitch refunds the full captured amount, so always send it for partial refunds. Refunds can finish later: the status starts as pending and you get a refund_succeeded or refund_failed webhook. See Refunds.

Response

{
  "refund_id": "ref_3b7e5a1c4d",
  "payment_id": "pay_8f2c1d9e0a",
  "amount": 50000,
  "currency": "VND",
  "status": "pending",
  "reason": "Customer returned one item"
}

Step 5: Receive and verify a webhook

When the payment succeeded in Step 2, VNSwitch sent a payment_succeeded event to your webhook URL. Each webhook carries an X-Webhook-Signature-512 header: the lowercase hex HMAC-SHA512 of the raw body, keyed with your payment response hash key.

  1. Read the raw body before parsing it.
  2. Compute the HMAC and compare it in constant time. Reject the request if it does not match.
  3. Reply with a 2xx status quickly, then process the event. Use event_id to ignore duplicates.

Done. You created, confirmed, checked and refunded a payment, and verified a webhook. Next, read Payment lifecycle and Webhooks, then the Go-live checklist.

Webhook body (example)

{
  "merchant_id": "merchant_vn_001",
  "event_id": "evt_01JBX7Q8M2",
  "event_type": "payment_succeeded",
  "content": {
    "type": "payment_details",
    "object": {
      "payment_id": "pay_8f2c1d9e0a",
      "status": "succeeded",
      "amount": 150000,
      "currency": "VND"
    }
  },
  "timestamp": "2026-11-01T08:30:02.000Z"
}