Skip to main content

Xendit Sandbox Setup

This project can run against real Xendit test-mode endpoints instead of WireMock for local payment development.

What This App Expects

The current integration:

  • sends authenticated server-side requests to Xendit
  • creates QR payments through /qr_codes
  • keeps subscription billing on a transitional path where renewal orchestration is handled in our backend domain model
  • receives payment status updates through POST /api/v1/payments/webhook
  • verifies the x-callback-token header on incoming webhooks

Relevant code:

  • src/server/outbound/xendit/http.ts
  • src/server/outbound/xendit/real-client.ts
  • src/server/payments/use-cases/create-payment.ts
  • src/server/subscriptions/use-cases/create-renewal-payment.ts
  • src/server/core/middleware/webhook-auth.ts

1. Create Xendit Test Credentials

  1. Create a Xendit account and stay in Test Mode.
  2. In the Xendit dashboard, generate a test Secret API Key.
  3. Give the key the permissions needed for money-in / payment creation.
  4. Store the secret key securely.

Official references:

2. Configure a Webhook in Xendit

Xendit must be able to call your app from the public internet, so a pure localhost URL is not enough.

  1. Start a public tunnel to your local API preview.
  2. Configure the webhook URL in Xendit as:
https://your-public-url/api/v1/payments/webhook
  1. Copy the webhook token from the Xendit dashboard.
  2. Set the same token in this app as XENDIT_WEBHOOK_TOKEN.

The app verifies the x-callback-token header and can also validate a webhook signature secret if you configure XENDIT_WEBHOOK_SIGNATURE_SECRET.

Official reference:

3. Start the App Against Real Xendit Sandbox

Run the local app without WireMock and point it at real Xendit test endpoints:

APP_BASE_URL=http://localhost:3000 \
API_BASE_URL=https://your-public-url \
NEXT_PUBLIC_API_BASE_URL=http://localhost:8787 \
XENDIT_API_URL=https://api.xendit.co \
XENDIT_SECRET_KEY=your_test_secret_key \
XENDIT_WEBHOOK_TOKEN=your_webhook_token \
./scripts/local.sh start

Notes:

  • APP_BASE_URL is the browser-facing app URL.
  • API_BASE_URL should be the public URL Xendit can reach for webhook callbacks.
  • NEXT_PUBLIC_API_BASE_URL remains local so the browser talks to your local preview server.
  • XENDIT_API_URL=https://api.xendit.co switches off WireMock and uses real Xendit test mode.

Expose the Workers preview port, not the Next.js UI port, because the webhook endpoint is served by the API runtime.

Example with ngrok:

ngrok http 8787

Then use the generated HTTPS URL as API_BASE_URL and as the Xendit webhook base URL.

5. Suggested Local Env File

You can also put the values in .env.local for repeat use:

APP_BASE_URL=http://localhost:3000
API_BASE_URL=https://your-public-url
NEXT_PUBLIC_API_BASE_URL=http://localhost:8787
XENDIT_API_URL=https://api.xendit.co
XENDIT_SECRET_KEY=your_test_secret_key
XENDIT_WEBHOOK_TOKEN=your_webhook_token

Then start the app normally:

./scripts/local.sh start

6. How To Test It

  1. Open http://localhost:3000.
  2. Go through the normal photobooth flow until payment creation.
  3. Confirm the app creates a QR payment successfully.
  4. Complete the payment in Xendit test mode if supported by your sandbox scenario.
  5. Verify the webhook reaches:
/api/v1/payments/webhook
  1. Confirm the app updates payment state and activates the license as expected.

7. Troubleshooting

If payment creation fails:

  • verify XENDIT_SECRET_KEY
  • verify XENDIT_API_URL=https://api.xendit.co
  • check server logs for provider error responses

If webhooks fail:

  • verify the public tunnel URL is still active
  • verify Xendit points to /api/v1/payments/webhook
  • verify XENDIT_WEBHOOK_TOKEN matches the dashboard token exactly

If the browser cannot talk to the local API:

  • verify NEXT_PUBLIC_API_BASE_URL=http://localhost:8787
  • verify the Workers preview runtime is running on port 8787

8. Notes About This Integration

  • This repo currently uses the Xendit /qr_codes integration path.
  • createMonthlySubscription() is still a transitional integration, not provider-native recurring billing.
  • The backend now models subscriptions, renewals, PAST_DUE, and recurring entitlements correctly, but provider-native recurring setup/cancel/resume is still future work.
  • Xendit documentation emphasizes newer payment APIs for some flows, so revisit the integration if you decide to modernize the payment implementation later.