Xendit Gateway for Gravity Forms

1. Overview

Xendit Gateway for Gravity Forms lets any Gravity Forms form take payments through Xendit, the leading payment gateway in Southeast Asia. When a customer submits a form, the add-on creates a Xendit Payment Session (Payments API v3) and redirects the customer to Xendit’s secure hosted payment page. There they can pay by card (with 3-D Secure), e-wallet, virtual account, QR or retail outlet. Card data never touches your website.

The add-on is built on the official Gravity Forms Payment Add-On Framework, so payment status, transaction ID, entry notes, notifications and the Sales report all work the standard Gravity Forms way.

2. Features

  • Xendit Payment Sessions hosted checkout
  • Cards (3DS), e-wallets, virtual accounts, QR, retail outlets
  • IDR, PHP, VND, THB, SGD, MYR, USD
  • Feeds with conditional logic
  • Billing mapping: first name, last name, email, phone
  • Verified, idempotent webhooks
  • Return-URL verification fallback
  • Full & partial refunds from the entry page
  • Declined and expired payment handling
  • Friendly “channel not activated” message and admin notice
  • Payment Completed / Failed / Refunded notification events
  • Test / Live mode, debug logging, translation-ready

3. Requirements

ItemRequirement
WordPress6.0 or later
Gravity Forms2.5 or later (any licence with the Payment Add-On Framework)
PHP7.4 or later
HTTPSRequired (Xendit return URLs and webhooks)
Xendit accountTest mode is free; live mode needs Xendit business verification
Form currencyIDR, PHP, VND, THB, SGD, MYR or USD (Forms → Settings → Currency)

4. Installation

  1. In WordPress go to Plugins → Add New → Upload Plugin.
  2. Choose gravityforms-xendit-1.0.0.zip, click Install Now, then Activate.
  3. A new Xendit tab appears under Forms → Settings.

5. Getting Xendit API keys

2FA is required. Xendit won’t generate a secret key until Two-Factor Authentication is enabled on your dashboard account, and it asks for a 6-digit code when you create the key.
  1. Sign in to the Xendit Dashboard and switch to Test mode.
  2. Open Settings → Developers → API Keys and click Generate secret key.
  3. Set Money-in products → Write and leave everything else on None.
  4. Copy the key straight away (test keys start with xnd_development_, live keys with xnd_production_).

6. Plugin settings

Go to Forms → Settings → Xendit.

SettingDescription
ModeTest (sandbox) or Live.
Test / Live Secret KeyYour Xendit secret API keys (Money-in Write).
Webhook Verification TokenFrom Xendit → Settings → Webhooks. Compared with the x-callback-token header on every webhook; webhooks with a wrong token are rejected with 401.
Webhook URLRead-only: https://<your-site>/?callback=gravityforms-xendit. Set it in Xendit for all event types listed in Webhook setup.
Debug logLogs webhook, API and info messages through Gravity Forms Logging. Errors are always logged.

7. Webhook setup

In Xendit Dashboard → Settings → Webhooks, paste https://<your-site>/?callback=gravityforms-xendit into each field below and click Test and save. Every field should return 200.

Xendit Webhooks sectionEventWhat the add-on does
Payment SessionPayment Session Completed (payment_session.completed)Required. Marks the entry Paid and saves the transaction ID.
Payment SessionPayment Session Expired (payment_session.expired)Required. Marks an unpaid entry Failed.
Payment Status (v3)payment.capture / payment.failureRecommended. Capture is a backup for completion; failure adds a “payment attempt failed” note.
Payment Request Statuspayment_request.expiryRecommended. Adds an entry note.
Refund Status / Unified Refundsrefund.succeeded / refund.failedRecommended. Records partial refunds; a full refund sets the entry to Refunded.
Legacy page: Payment Requests V2Payment Succeeded, Payment Failed, Captured SucceededRecommended on accounts that still show the legacy webhook page.
Xendit only delivers event types that have a URL. Any event without one appears as URL Not Set in Xendit’s Webhook Logs and never reaches your site.
The dashboard’s Test and save button sends sample data. The add-on detects these pings, answers 200 and logs them at info level, so they never touch your entries.
Currency: the Gravity Forms currency (Forms → Settings → Currency) must match a currency enabled on your Xendit account. Otherwise Xendit refuses the session.

8. Creating a Xendit feed

  1. Your form needs pricing fields (Product, Total, etc.) and an Email field. A Name and Phone field are recommended.
  2. Open the form and go to Settings → Xendit → Add New.
  3. Transaction Type: Products and Services. Subscriptions aren’t offered because Xendit Payment Sessions take one-time payments.
  4. Payment Amount: Form Total, or a single product field.
  5. Billing Information: map First Name, Last Name, Email (required) and Phone. Xendit pre-fills these on the hosted page.
  6. Notifications (optional): tick Send notifications only when payment is received and choose which form notifications to hold. They are sent once Xendit confirms the payment.
  7. Post Payment Actions (optional): for add-ons such as Mailchimp, Zapier or User Registration, tick Process <add-on> feed only when payment is received. Those feeds then run after the payment is confirmed instead of on submission.
  8. Phone format: Xendit needs phone numbers in E.164 format (for example +919876543210). Map the optional Country billing field (an Address field country works) so the add-on can add the right country code. Otherwise it uses the Address field’s default country, then the Default phone country plugin setting (for example IN). A number that can’t be formatted is left out, and the customer types it on the Xendit page.
  9. Conditional Logic (optional): only send the entry to Xendit when your conditions match.
  10. Save the feed.

Step-by-step setup with screenshots

The Xendit dashboard steps are identical for WooCommerce and Gravity Forms. Where a screenshot shows a /wc-api/… webhook URL, use the Gravity Forms URL https://<your-site>/?callback=gravityforms-xendit instead. Keys and tokens are blurred.

Part A: Xendit Dashboard

  1. Open Settings → Developers → API Keys (Test mode)

    Make sure the yellow Test Mode banner shows. Only a secret key is needed; the public key isn’t used.

    Xendit API keys
    API Keys page (public key blurred).
  2. Enable 2FA if prompted

    Xendit asks you to enable 2FA before it will generate a secret key.

    2FA required
    2FA is required to generate keys.
  3. Generate a secret key with Money-in Write

    Name the key, set Money-in products to Write, click Generate key and enter your 2FA code. Copy the key.

    Secret key permissions
  4. Reveal the webhook verification token

    Under Webhooks → Webhook verification token, click View Webhook Verification Token and copy it.

    Webhook token section
  5. Find the Payment Session webhook fields

    Payment Session Completed and Payment Session Expired are both required.

    Payment Session fields
  6. Enter the webhook URL and click Test and save

    Use https://<your-site>/?callback=gravityforms-xendit. Once the token is saved in Gravity Forms, Test and save returns 200.

    Webhook test 200
    Example 200 response (this screenshot shows the WooCommerce URL; use the Gravity Forms URL).
  7. Set the same URL for every other event

    Payment Status, Payment Request Status, Refund Status and the legacy Payment Succeeded / Failed / Captured Succeeded fields.

    All webhook events
    Legacy and refund events with a URL set.

Part B: Gravity Forms

  1. Install and activate the add-on

    Plugins → Add New → Upload Plugin → gravityforms-xendit-1.0.0.zip.

    Plugin activated
    Xendit Gateway for Gravity Forms 1.0.0 activated (WordPress 7.1.3, Gravity Forms 2.10.3).
  2. Enter the plugin settings

    Forms → Settings → Xendit: Mode, Test Secret Key, Webhook Verification Token. Copy the Webhook URL.

    Plugin settings
    Forms → Settings → Xendit with the key and token saved (masked).
    Plugin settings bottom
    Bottom of the settings: Webhook URL to copy into Xendit and the debug log option.
    Xendit webhooks
    The same Webhook URL entered for Payment Session and Payment Status events in Xendit.
  3. Create the feed

    Form → Settings → Xendit → Add New: Products and Services, Payment Amount, billing mapping, conditional logic.

    Form editor
    Example form: Name, Email, Product, Phone and Total.
    Feed list
    The Xendit feed listed on the form.
    Feed settings
    Xendit feed (top): Transaction Type Products and Services, Payment Amount and billing field mapping. No Sample Option.
    Feed settings bottom
    Xendit feed (bottom): other settings, conditional logic and Notifications held until payment is received.
  4. Submit the form

    The customer fills in the form and is redirected to the Xendit hosted page.

    Form front end
    Form on the front end (email and phone blurred).
  5. Pay on Xendit

    The customer pays (3DS card shown) and returns to your confirmation message.

    Xendit hosted page
    Xendit hosted card form with name, email and phone pre-filled (email blurred).
    Xendit 3DS
    3D Secure step for the test card.
    Confirmation
    Back on the site: the form confirmation.
  6. Entry is Paid

    The entry shows Payment Status Paid, the transaction ID and a “Xendit payment completed” note.

    Entry paid
    Entry #9598 Paid, with the Xendit transaction ID in Payment Details (IDs, email and phone blurred).
    Entry notes
    Entry notes: payment completed and the delayed admin notification sent after payment (IDs blurred).
    Webhook 200
    Xendit Webhook Logs: payment_session.completed delivered with HTTP 200.
  7. Refund via Xendit

    On the entry, use the Refund via Xendit box for a full or partial refund.

    Partial refund
    Refund via Xendit box on entry #9599 ($4.20 paid): enter an amount for a partial refund, or leave it blank to refund everything left.
    Partial refund notes
    After a partial refund on entry #9599: Refundable drops straight to $3.20 (and stays there after a reload), the note shows the Xendit refund request with status SUCCEEDED, and the entry stays Paid (IDs blurred).
    Full refund
    Full refund of the remaining balance: the entry becomes Refunded.
    Xendit transactions
    The payment and its refund in Xendit → Transactions (references blurred).

9. Payment flow & entry statuses

  1. Form submitted → entry created → Payment Session created → entry Processing with note “Xendit Payment Session created”.
  2. Customer pays on Xendit → payment_session.completed webhook → Gravity Forms complete_payment → entry Paid, transaction ID set, “Payment Completed” notifications sent.
  3. The customer returns to the form page. The add-on verifies the session with GET /sessions/{id} and completes the entry if the webhook hasn’t arrived yet, then shows your confirmation.

Every event has a unique action ID, and the framework’s callback log ensures it is processed only once. Webhook and return-URL processing never double-complete an entry.

10. Transaction ID

The entry’s Transaction ID is the Xendit payment ID (py-…). The Payment Session ID (ps-…) and Payment Request ID (pr-…) are saved as entry meta and in the notes. Search for the payment ID in Xendit → Transactions.

Transaction ID
Payment Details box with the Xendit transaction ID.

11. Refunds

Gravity Forms has no built-in refund button for payment add-ons, so this add-on adds a Refund via Xendit box to the entry detail page (visible on paid Xendit entries).

  • Leave the amount blank for a full refund of the remaining amount, or enter a smaller amount for a partial refund. Over-refunds are blocked.
  • The add-on calls POST /refunds and adds a “refund requested” note.
  • When Xendit confirms with refund.succeeded, a partial refund adds a note and the entry stays Paid. A refund that brings the total to the full amount sets the entry to Refunded.
  • refund.failed adds an error note.
Not every channel supports refunds or partial refunds. If Xendit refuses, refund from the Xendit Dashboard instead.
Over-refund error
Entering more than the Refundable balance (here $5.00 against $3.20 on entry #9599) is blocked with the error “Refund amount exceeds refundable balance ($3.20).” at the top of the entry page and inside the Refund via Xendit box. A blank or zero amount shows “Enter a refund amount greater than zero.” (email, phone, IP and transaction ID blurred).

12. Pending, declined & expired payments

SituationEntry payment statusEntry note
Customer is on the Xendit page or left without payingProcessingXendit Payment Session created
Card declined / attempt failed (payment.failure)Stays Processing (the customer can retry on the same page)Xendit payment attempt failed: <failure_code> (<channel>)
Session expired (payment_session.expired)FailedExpired without payment (abandoned) / after N failed attempts
Customer clicked back / cancel on XenditProcessingCustomer returned from Xendit without completing payment

Paid and refunded entries are never changed by later failure or expiry events.

Payment failed
A declined test card on the Xendit page.
Declined note
The entry gets “payment attempt failed: AUTHENTICATION_FAILED (CARDS)” and stays Processing.
Abandoned
An abandoned checkout stays Processing until the session expires.

13. Payment channels & INVALID_PAYMENT_CHANNEL

Xendit only shows channels that are activated for your business country and currency. Activate them in Xendit Dashboard → Settings → Payment Channels.

If Xendit refuses to create a session with INVALID_PAYMENT_CHANNEL:

  • the customer sees “Sorry, this payment method is not available right now. Please try again later or contact us.” instead of a technical error;
  • the entry gets an error note and is marked Failed;
  • administrators see a dismissible notice with a link to the Xendit Dashboard.

Developers can restrict channels with the gform_xendit_session_payload filter (for example, by adding allowed_payment_channels).

14. Notifications

In Form → Settings → Notifications, choose the event Payment Completed, Payment Failed or Payment Refunded. This way you only email the customer a receipt once Xendit has confirmed the payment.

You can also hold any existing form notification until payment: in the Xendit feed, tick Send notifications only when payment is received and choose the notifications to delay. In the same feed, Create post only when payment is received and Post Payment Actions delay post creation and other add-on feeds (Mailchimp, Zapier, User Registration…) until Xendit confirms the payment.

15. Debug logs

  1. Turn on Debug log in Forms → Settings → Xendit.
  2. Enable logging in Forms → Settings → Logging and select Xendit Gateway for Gravity Forms.
  3. View or download the log from the Logging page.

API calls, webhook events and test pings are logged as info and debug lines; errors are always logged. Secret keys and tokens are never written to the log.

Debug log
Gravity Forms log for the add-on: complete_payment and refund_payment actions (ids blurred).

16. Troubleshooting & FAQ

The Xendit feed can’t be created

Save a secret key for the current mode in Forms → Settings → Xendit first.

Entries stay Processing after payment

Check that the webhook URL is set for Payment Session Completed, that the token matches, and that Test and save returns 200. The return-URL check also completes the entry when the customer comes back.

Webhook returns 401

The Webhook Verification Token in Gravity Forms doesn’t match the one in Xendit.

“This payment method is not available right now”

Activate the payment channels in Xendit (see section 13).

Currency not supported

The Gravity Forms currency must match a currency enabled on your Xendit account (for example a USD form needs USD activated in Xendit). Set Forms → Settings → Currency to IDR, PHP, VND, THB, SGD, MYR or USD. IDR and VND are added to the list by the add-on.

Subscriptions?

Not supported. Xendit Payment Sessions are one-time payments.

17. Tested on

  • Live Gravity Forms sandbox: gravityforms.patsatech.com (WordPress 7.1.3, Gravity Forms 2.10.3, Xendit Test mode, USD; PHP 7.4+ required, code checked on PHP 8.4)
  • Paid card payment: entry Paid, transaction ID saved
  • Partial refund: Refundable balance drops immediately and stays after a reload, entry stays Paid, shown in Xendit
  • Refund larger than the Refundable balance: rejected with the admin error “Refund amount exceeds refundable balance”
  • Full refund of the remainder: entry Refunded, shown in Xendit
  • Declined card: “payment attempt failed: AUTHENTICATION_FAILED (CARDS)” note, entry stays Processing
  • Abandoned checkout: entry stays Processing until the session expires
  • All Xendit webhooks answered HTTP 200

18. Changelog

1.0.0 — 2026-10-16

  • Initial release.

19. Support

PatSaTECH
Email: [email protected]
Web: https://www.patsatech.com

Please include your Gravity Forms and PHP versions, a screenshot of your Xendit webhook settings, and the relevant log lines (with secrets removed).

Our Partners

fraudlabs
opayo
nochex
Razorpay
durango merchant services
2checkout is now verifone
authorizenet
gravity forms
whmcs