discord_username during checkout. When the customer pays, the payment.success webhook event is triggered. You can then fetch the order to retrieve the submitted discord_username value and automate the role assignment.
Other examples include collecting license keys, server preferences, referral codes, or any data your fulfillment logic needs.
Grab the secrets
In your shop, create a new webhook. The webhook secret will look like this: in our
wh_sk_xxx. Copy and securely store the webhook secret.You’ll also need your Storefront ID for the X-Pandabase-Storefront header.We are storing these secrets as:.env
.env file.Prepare backend
Set up an Express server with TypeScript. First, initialize your project and install the necessary dependencies:Create a
src folder and add an index.ts file:src/index.ts
Define custom fields in the checkout session
When creating a checkout session via the initialize endpoint, pass a
The response includes the checkout URL. Redirect your customer there.
custom_fields array. Each field needs a key, label, type, and optional constraints.There are three field types:| Type | Description | Config options |
|---|---|---|
text | Free-form text input | default_value, minimum_length, maximum_length |
numeric | Digits-only input | default_value, minimum_length, maximum_length |
dropdown | Select from a list | default_value, options (array of label/value pairs) |
Handle payment.success with custom fields
When a payment succeeds, the webhook fires a Each entry in
payment.success event. The event.data.order object contains the custom_fields array with both the field definitions and the customer’s submitted values merged together.custom_fields on the order looks like:Field Definition Reference
| Property | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique identifier, alphanumeric and underscores, max 200 chars |
label.type | string | Yes | Must be "custom" |
label.custom | string | Yes | Display label shown to customer, max 50 chars |
type | string | Yes | "text", "numeric", or "dropdown" |
optional | boolean | No | Default: false |
text.default_value | string | No | Default value for text fields |
text.minimum_length | integer | No | Minimum character length (min: 0) |
text.maximum_length | integer | No | Maximum character length (max: 255) |
numeric.default_value | string | No | Default value for numeric fields |
numeric.minimum_length | integer | No | Minimum digit length (min: 0) |
numeric.maximum_length | integer | No | Maximum digit length (max: 255) |
dropdown.default_value | string | No | Pre-selected option value |
dropdown.options | array | Yes | 1-200 items, each with { label, value } |
Validation Rules
The server strictly validates submitted values:- Required fields must have a non-empty value
- Text: enforces
minimum_lengthandmaximum_lengthconstraints - Numeric: value must contain only digits, enforces length constraints
- Dropdown: value must match one of the defined
options[].valueentries - Duplicate keys are rejected
- Unknown keys not defined in the session are rejected
- Submitting
custom_fieldswhen the session has none defined returns an error - Maximum of 3 custom fields per session
Webhook Headers
Every webhook delivery includes these headers:| Header | Description |
|---|---|
X-Pandabase-Signature | HMAC-SHA256 signature of the JSON body using your webhook secret |
X-Pandabase-Timestamp | Unix timestamp of the delivery |
X-Pandabase-Idempotency | Unique delivery ID for deduplication |
