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.md Step 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, handling plugipay.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

  1. Open Settings → Developers → API Keys in the Xendit Dashboard.
  2. Click + Generate Key. Name it specifically — plugipay-test or plugipay-prod. Separate keys per environment makes revocation simple.
  3. Under Permissions, enable:
    • money_in:write (required — creates invoices, e-wallet charges, VA)
    • money_out:write (required — refunds, disbursements)
  4. 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

  1. Still in Settings → Developers, open the Callbacks tab.
  2. Copy the Callback Verification Token. Xendit attaches this as X-Callback-Token on 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_... or xnd_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:write means refunds via Plugipay return 403 forbidden from 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

  1. In the Plugipay Dashboard, go to Settings → Providers → + Add provider → Bring your own Xendit.
  2. 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.
  3. Click Test connection. Plugipay calls Xendit GET /balance. On success, the button becomes Save & continue; on failure, the Xendit error surfaces inline.
  4. 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

  1. 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.

  2. In Xendit, go to Settings → Developers → Webhooks → + Add URL.

  3. 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 livemode flag 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:

  1. All five must be enabled. Skipping any one breaks a method family — no ewallet.payment means OVO / DANA / ShopeePay / LinkAja charges never reach completed in Plugipay.
  2. 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 outbound plugipay.checkout_session.completed.v1.


7. Step 5 — Sandbox test charge

Heading

Step 5 · Fire a test charge end to end

Goal

Create a CheckoutSession via 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.v1 event with data.object.adapter = "xendit" and status = "completed". In the Xendit Dashboard under Transactions, the matching invoice is marked paid. In Plugipay under Events → Deliveries, the outbound webhook shows 200.

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_invalid when 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_key on 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.

Plugipay — Payments that don't tax your success