Plugipay — BYO Xendit Provider Guide (/docs/providers/xendit) Copy
- Project: plugipay (M4, Forjio split)
- Source brief:
spec/prd.md§10 ·copy/landing-copy.md(voice anchor) ·copy/docs/providers/managed.md(sibling doc) ·copy/docs/quickstart-dev.md(voice + step substructure) ·copy/onboarding.mdStep 1 (Xendit field names) · Xendit API Keys / Callbacks / Webhooks reference docs - Audience: Developers or technical founders who already own — or are willing to own — a Xendit account and want the settlement relationship in their name. Ecosystem norm: English.
- Voice anchor:
copy/landing-copy.md— "Stripe circa 2014, before corporate polish." Three voice rules: (1) admit trade-offs, (2) show with a runnable command, (3) no success theatre. - Register: Imperative mood. Terse. Numbers and exact paths before prose. No second-person marketing. No
journey,seamlessly,empowering,unlock,best-in-class,revolutionary,next-generation,ecosystem solution. - Technical terms: full English.
Xendit,QRIS,VA,OVO,DANA,ShopeePay,webhook,sandbox,test mode,live mode,callback token— keep verbatim. Entity names PascalCase:CheckoutSession,Ledger,PaymentToken.
Information architecture
| # | Block | Goal |
|---|---|---|
| 1 | Hero | Name the path, set the "10-minute connect" promise |
| 2 | Prerequisites | Xendit account, Plugipay on Growth+ |
| 3 | Step 1 | Generate secret key + copy callback token in Xendit |
| 4 | Step 2 | Paste credentials into Plugipay |
| 5 | Step 3 | Register Plugipay webhook URL in Xendit |
| 6 | Step 4 | Subscribe to the five events |
| 7 | Step 5 | Sandbox test charge end-to-end |
| 8 | Troubleshooting | Signature, webhook delivery, test/live mismatch |
| 9 | Placement notes | Frontend route + component mapping |
Five numbered steps, each with goal / actions / success checkpoint / pitfall.
1. Hero
Eyebrow
Providers · Bring your own Xendit
Headline (7 words)
Connect your Xendit account in ten minutes.
Subheadline (one sentence, 28 words)
Generate one secret key, paste it into Plugipay, register one webhook URL, subscribe to five events, and fire a sandbox charge — your contract with Xendit stays yours.
Trade-off line (voice rule #1)
BYO Xendit means you own the relationship — settlement, KYC, pricing, disputes. Plugipay doesn't take custody of funds. If you'd rather skip the Xendit dashboard entirely, use
Plugipay managed— same API, zero setup.
What we'll subscribe to (right column, pinned on desktop)
invoice.paid
invoice.expired
ewallet.payment
virtual_account.credited
direct_debit.payment_completed
Primary CTAs
- Start with step 1 →
#step-1(brand-700 button) - Compare with Plugipay managed →
/docs/providers/managed(text link)
2. Prerequisites
Before you start.
- A Xendit account. Sign up at dashboard.xendit.co/register — onboarding is 5–10 minutes plus KYC for live mode.
- A Plugipay account on Growth tier or higher. BYO provider routing is gated on Starter; upgrade at Settings → Plan.
- Ten minutes, two browser tabs (Xendit + Plugipay).
You do not need to write code here. Connection is dashboard-to-dashboard. Code (creating a
CheckoutSession, handlingplugipay.checkout_session.completed.v1) is covered in the developer quickstart. New Xendit accounts land in test mode — keep it there until you finish §7 sandbox test.
3. Step 1 — Generate a secret key and copy your callback token
Heading
Step 1 · Get the two credentials Plugipay needs
Goal
Create a secret key with
money_in:write+money_out:write, then copy the callback token Xendit already generated for your account.
Actions — secret key
- Open Settings → Developers → API Keys in the Xendit Dashboard.
- Click + Generate Key. Name it specifically —
plugipay-testorplugipay-prod. Separate keys per environment makes revocation simple. - Under Permissions, enable:
money_in:write(required — creates invoices, e-wallet charges, VA)money_out:write(required — refunds, disbursements)
- Click Generate. Xendit shows the secret once. Copy it to a password manager immediately.
[Screenshot: Xendit Dashboard — Settings → Developers → API Keys → "+ Generate Key" permissions panel]
Actions — callback token
- Still in Settings → Developers, open the Callbacks tab.
- Copy the Callback Verification Token. Xendit attaches this as
X-Callback-Tokenon every webhook — Plugipay uses it to verify authenticity.
[Screenshot: Xendit Dashboard — Settings → Developers → Callbacks → Callback Verification Token with copy button]
Success checkpoint
You have two strings saved: a secret key (
xnd_development_...orxnd_production_...) and a callback token. Both belong in a password manager, not Slack, email, or Git.
Pitfall — secret shown once
Xendit does not re-reveal the secret after the modal closes. If you lose it, use Revoke & regenerate on the row and update Plugipay immediately.
Pitfall — missing money_out:write
Enabling only
money_in:writemeans refunds via Plugipay return403 forbiddenfrom Xendit. Add both scopes to one key — Plugipay surfaces Xendit's error verbatim.
4. Step 2 — Paste credentials into Plugipay
Heading
Step 2 · Connect the key in Plugipay
Goal
Open the BYO Xendit connect form, paste the two values, test the connection.
Actions
- In the Plugipay Dashboard, go to Settings → Providers → + Add provider → Bring your own Xendit.
- Fill the form:
- Xendit secret key — paste the secret from §3.
- Callback verification token — paste the token from §3 (optional, but leaving it blank means Plugipay can't verify webhook authenticity — don't).
- Test mode — leave on.
- Click Test connection. Plugipay calls Xendit
GET /balance. On success, the button becomes Save & continue; on failure, the Xendit error surfaces inline. - Click Save & continue.
[Screenshot: Plugipay Dashboard — Settings → Providers → Bring your own Xendit connect form, "Test connection" success state]
Success checkpoint
The provider row shows Xendit · active · test mode with the last 4 characters of the secret masked.
Pitfall — trailing whitespace / truncated paste
The Xendit modal sometimes truncates the key visually, losing the last character silently. Paste into a plain-text editor first, verify length (prefix + 48 chars), then paste into Plugipay.
5. Step 3 — Register Plugipay's webhook URL in Xendit
Heading
Step 3 · Point Xendit webhooks at Plugipay
Goal
Add Plugipay's ingress URL to Xendit's webhook list so completions reach Plugipay.
Actions
In Plugipay, go to Settings → Providers → Xendit and copy the webhook URL:
https://plugipay.com/webhooks/xendit/{merchant_id}Plugipay substitutes
{merchant_id}automatically — copy the rendered URL, not the template.In Xendit, go to Settings → Developers → Webhooks → + Add URL.
Paste the URL, method
POST, save.
[Screenshot: Xendit Dashboard — Settings → Developers → Webhooks → "+ Add URL" form with Plugipay URL pasted]
Success checkpoint
Xendit's Webhooks page lists Plugipay's URL with status Active.
Pitfall — test-mode vs live webhook lists
Xendit maintains separate webhook lists for test and live mode. Add the URL in both when you flip. Plugipay's ingress URL is identical across environments — the
livemodeflag in the payload tells Plugipay which side the event came from.
6. Step 4 — Subscribe to the events Plugipay consumes
Heading
Step 4 · Enable five webhook events
Goal
Tell Xendit which event types to deliver to Plugipay's ingress URL.
Actions
On the webhook you added in §5, enable exactly these five event types:
invoice.paid
invoice.expired
ewallet.payment
virtual_account.credited
direct_debit.payment_completed
Save.
Two things to watch for:
- All five must be enabled. Skipping any one breaks a method family — no
ewallet.paymentmeans OVO / DANA / ShopeePay / LinkAja charges never reachcompletedin Plugipay. - Extras are noisy but harmless. Plugipay ignores unknown types; they still clutter Xendit's delivery log. Subscribe only to the five unless you're debugging.
[Screenshot: Xendit Dashboard — Settings → Developers → Webhooks → event checklist with the five Plugipay events selected]
Success checkpoint
The webhook row shows 5 events enabled matching the list above.
Pitfall — payment.succeeded does not exist on Xendit
Xendit's event names are method-specific — there is no unified
payment.succeeded. Subscribe to the five above, not a generic "success" event. Plugipay maps all five onto its own outboundplugipay.checkout_session.completed.v1.
7. Step 5 — Sandbox test charge
Heading
Step 5 · Fire a test charge end to end
Goal
Create a
CheckoutSessionvia CLI, complete a QRIS payment in the Xendit sandbox, watch the event reach Plugipay.
Actions
Terminal 1 — create a session:
plugipay checkout create \
--amount 50000 \
--currency IDR \
--methods qris,va,ewallet \
--success-url https://example.com/thanks \
--cancel-url https://example.com/cancel \
--mode test \
--provider xendit \
--json
Response:
{ "id": "cs_01HXZ...", "hostedUrl": "https://plugipay.com/c/cs_01HXZ...", "status": "open" }
Terminal 2 — listen for the completion event:
plugipay events listen --types plugipay.checkout_session.completed.v1 --mode test
Open hostedUrl in a browser, pick QRIS, complete the Xendit sandbox flow.
Success checkpoint
Terminal 2 prints a
plugipay.checkout_session.completed.v1event withdata.object.adapter = "xendit"andstatus = "completed". In the Xendit Dashboard under Transactions, the matching invoice is marked paid. In Plugipay under Events → Deliveries, the outbound webhook shows200.
Pitfall — sandbox completes instantly
Xendit's sandbox auto-completes QRIS and e-wallet test charges without a real payer app — don't expect the "scan with GoPay" experience. If the event never fires, the issue is in steps §5 or §6 (webhook URL or subscriptions), not the payer.
8. Troubleshooting
Pitfall — invalid signature / X-Callback-Token mismatch
Plugipay rejects Xendit webhooks with
401 signature_invalidwhen the header token doesn't match the one saved in §4. Usually you regenerated it in Xendit without updating Plugipay, or pasted the secret key into the wrong field. Re-copy from Settings → Developers → Callbacks and re-save in Plugipay.
Pitfall — webhook not received
Check Xendit Settings → Developers → Webhooks → Delivery log: non-2xx means Plugipay rejected (check signature); no row means §6 is missing. In Plugipay, Events → Deliveries (direction
inbound) confirms receipts. Xendit retries with backoff for 24 hours, so transients self-heal.
Pitfall — test-mode vs live mismatch
The most common support ticket. Xendit keys, webhook lists, and callback tokens are separate between test and live. Flipping Plugipay to live means repeating §3–§6 in live mode — new secret, new webhook URL entry, same five events, Plugipay provider row set to live. Miss one and live charges silently stay
open.
Pitfall — 401 invalid_api_key after rotation
Revoking the secret in Xendit without updating Plugipay returns
401 invalid_api_keyon the next call. Update via Settings → Providers → Xendit → Edit; in-flight sessions reconcile once the key is valid again.
If your error isn't listed, email support@forjio.com with your Account ID (acc_...) and the step you hit. We don't send canned answers.
9. Placement notes
All sections render in code/frontend/src/app/(marketing)/docs/providers/xendit/page.tsx as scroll-anchored blocks (#hero, #prerequisites, #step-1–#step-5, #troubleshooting). Sidebar nav from docs/index.md §3.3 Providers. Screenshot assets → code/frontend/public/docs/providers/xendit/ (PNG, 2x) — Iro/Hikari during implementation. Shared primitives (code-block.tsx, callout.tsx, step-badge.tsx) reused from /docs/quickstart-dev. Cross-page links: /docs/providers/managed (shipped), /docs/providers/{midtrans,paypal} (placeholder for MVP), /docs/quickstart-dev, /docs/pricing, external https://dashboard.xendit.co, mailto:support@forjio.com.
End of docs/providers/xendit.md.