Transaction custom fields
Define your own fields, collect them on a payment page or through the API, and carry them on the transaction record.
A custom field is a field you define yourself and attach to a payment. You choose the name, the format, and where it appears; WinkPG collects the value alongside the payment and carries it on the transaction record from then on. An order number, an invoice reference, a customer account code, a department, a booking id: anything your business needs to see next to the money is a good candidate.
Custom fields are defined per merchant, so one set of definitions applies to every payment page and every API call for that merchant.
Define your fields
Two places manage definitions, and they edit the same list.
On the merchant record. A reseller managing a merchant opens the merchant's record and edits its custom fields there, in a grid with a popup editor for each field.
In your own settings. If your reseller allows merchant self-service configuration (it's on by default), you'll find Tools > Custom Fields in the menu, at /MerchantSettings/CustomFields. It's the same grid and the same popup editor, scoped to your own merchant, and it saves with the Save button in the page toolbar. If self-service configuration isn't turned on for your merchant, the page explains that and your reseller can make the changes for you.
Either way, adding a field opens a popup with the options below.
What you can configure on a field
| Option | What it does |
|---|---|
| Name | The field's identifier: 3 to 50 characters, made up of letters and numbers. You can use an underscore as a separator between words, as in invoice_ids or pay_by_link_id, but not at the start or end of the name and not two in a row. This is the name the API expects and the key the value is stored under. |
| Description | Up to 100 characters. It doubles as the label a payer sees on a payment page, so write it for the payer. When it's blank, the name is used instead. |
| Form Position | A number that sets display order (lower numbers first). Fields with the same position fall back to alphabetical order by name. |
| Required | Whether a payer must fill the field in before a payment page will submit. |
| Enabled | Whether the field is active. Turning a field off retires it without losing the values already captured on past transactions. |
| Numeric | Restricts the field to numbers and switches on the numeric options below. |
| Min Value / Max Value | For numeric fields, the accepted range. Negative bounds are allowed, since a custom field is your own metadata rather than a monetary amount. |
| Decimal Places | For numeric fields, 0, 1, or 2. Zero means whole numbers only. |
| Max Length | For text fields, the longest value you want to accept, from 1 to 300. Leave it at 0 to accept the full 300. |
| Regex and Regex Message | For text fields, a regular expression the value must match, plus the message to show when it doesn't. A pattern like ^INV-[0-9]+$ is the usual way to pin a field to a house format. |
| Hosted Payment Pages | Presence settings for the payment page surfaces: Visible puts the field on the page, ReadOnly displays it without letting the payer edit it, and Accept a value from the session API lets an integration supply a value for a field the payer never sees. |
| Transaction Reports | Visible lists the field on the transaction detail page and on receipts. Clearing it hides the field from those views without affecting what's captured: the value stays on the transaction record and on the API response. |
| Show on save card pages | Whether the field is offered on a page whose purpose is to store a card rather than charge it. Off by default, since charge details such as an invoice number don't apply when nothing is being charged. |
| Multi-value and Max Values | Lets a transaction carry a list of values for the field instead of one. See Multi-value fields below. A multi-value field can't also be numeric. |
Where values are captured
Hosted Payment Pages. Both the Classic and the Streamlined public forms render your enabled, page-visible custom fields, in Form Position order, labeled with each field's description. Read-only fields are shown but not editable, which makes them a good fit for a value you prefill through the session or the query string and want the payer to see but not change.
The transaction API. A create request carries a customFields collection, each entry a name and a value (or, for a multi-value field, a values list). The name is the name from your definition; matching ignores case.
Attach a value the payer never sees
Some values belong on the transaction but not on the screen. An internal reference number is the usual case: you want it back on the transaction and the webhook, and the payer has no reason to see it, let alone change it.
Turn Visible off and Accept a value from the session API on. The field then never appears on a payment page, but an integration can supply its value when it creates the payment session, and the platform writes that value onto the resulting transaction. It shows up everywhere a captured custom field shows up: the transaction detail page, reports, and the completion webhook.
Two things follow from the payer never seeing the field:
- The value is checked when the session is created. Nobody can correct it afterward, so a value that breaks the field's Max Length, Regex, or numeric range is rejected on the session call rather than carried and dropped at payment time.
- Required does nothing here. A payer can't fill in a field that isn't rendered, so marking a hidden field required never blocks a payment.
Turning Visible off doesn't exempt the field from page scoping. If the page restricts which custom fields it accepts, the field's name has to be in that page's list too, and because the field never renders it isn't offered in the page builder's picker: your developers add it through the API. A page that shows all of your custom fields needs nothing extra.
Leaving the setting off keeps today's behavior: a value sent for a hidden field is either rejected or ignored, depending on whether the page scopes its custom fields.
Your developers can find the request shape in the Hosted payment page iframe integration guide.
Multi-value fields
Sometimes one payment settles several things, and you want each of their identifiers on the transaction. A single value is capped at 300 characters, which doesn't fit fifty invoice numbers, and packing them into one delimited string leaves the parsing to whoever reads it back. A multi-value field carries them as a list instead.
Turn Multi-value on in the field's popup. Max Values sets how many values a transaction may carry: leave it blank for 50, or set anything from 1 to 250. Every other option on the field keeps its meaning and applies to each value on its own: Max Length and Regex are checked per value, Required means at least one value, and the name rules are unchanged. Numeric is cleared when you turn Multi-value on, since a list of numbers isn't supported.
On the transaction API, send the list in values instead of value:
{
"customFields": [
{ "name": "invoice_ids", "values": ["INV-1001", "INV-1002", "INV-1003"] }
]
}
Each item is trimmed, has to be non-empty, and has to satisfy the field's Max Length and Regex. Duplicates are allowed and the order you send is the order stored. A list sent for a field that isn't multi-value is rejected, and so is a list longer than the field's Max Values.
value still works on a multi-value field. Send value on its own and the platform stores it exactly as sent and also fills values with that one item, so what you read back is a superset of what you sent. Send values and value stays as you sent it (normally absent). Sending both with different content is rejected, naming the field. Nothing about value changes on a field that isn't multi-value.
On a payment page session, prefill a list through prefilledListFields, a sibling of prefilledFields keyed the same way:
{
"hostedPageId": "00000000-0000-0000-0000-000000000000",
"prefilledFields": { "base_amount": "125.00" },
"prefilledListFields": { "invoice_ids": ["INV-1001", "INV-1002"] }
}
A key has to name one of your multi-value fields; a single-value field, a well-known key such as base_amount, a blocked key, or a key you also put in prefilledFields is refused, and the message names the key. The list is checked against the field's rules when the session is created, whether the field is visible or hidden, because the payer can't edit a list on the page. Both prefill maps count toward the same 50-key limit.
On the page, a visible multi-value field shows the prefilled list read-only, one value per line, and submits it with the payment. A visible multi-value field with nothing prefilled shows nothing and submits nothing: in this release a payer can't type a list in. A field set to Accept a value from the session API with Visible off takes a list the same way it takes a single value: the payer never sees it, and the platform writes it onto the transaction.
On the Virtual Terminal, a multi-value field is a text box that takes one value per line. Blank lines are ignored, and a required multi-value field needs at least one line.
Where the list appears. The transaction detail page and the receipt list one value per line. The API returns values on the transaction record and in the completion webhook. Where only one line is available, such as the custom field grid, the values are joined with a comma and a space. A value stored before you turned Multi-value on keeps rendering as it was captured, and a list keeps rendering after you turn it off; the stored shape decides how a value displays, not the current definition.
The v1 API has no list type. A v1 transaction response joins the list with a comma and a space in the field's value, and v1 requests and the v1 definition endpoints can't send a list or set the new options; use the current API for those.
Scope a page to a subset of your fields
Definitions are merchant-wide, but an individual payment page doesn't have to show all of them. Every page builder has a Custom Fields step (a step in Guided Setup, a section in the Classic and Streamlined editors) with two choices: show all of the merchant's custom fields, which is the default, or show a selected subset. Pick the subset when a page has a narrow job: a donation page probably wants a campaign code and nothing else, even though your merchant defines a dozen fields for the main checkout.
Only enabled, page-visible fields are offered for selection. A field set to accept a value from the session API isn't offered here, because it never renders; adding one to a page's list is something your developers do through the API. If one of a page's selected fields later stops being available (renamed, disabled, or hidden from payment pages), the builder says which selections it removed the next time you open the page.
How values are validated
Two layers apply, and a value that satisfies the page also satisfies the API.
Everywhere a value is submitted:
- The name must match one of your enabled definitions once you have at least one. Matching ignores case, so
OrderNumberandordernumberreach the same field. - Names are accepted up to 50 characters and values up to 300 characters. That's room for a UUID, a list of invoice references, or a tenant identifier from your own system.
- Control characters are rejected in both the name and the value. Ordinary printable punctuation is accepted, which is what lets an identifier like
INV-2026-1010through. - On a public payment page, the accepted set is narrowed further to the fields that page is scoped to, so a page that shows three fields accepts exactly those three.
On a payment page, each field's own rules apply as well:
- A required field has to be filled in.
- A text value has to fit the field's Max Length, up to the 300 character ceiling above, and match the field's regex if one is set. When it doesn't match, the payer sees your Regex Message.
- A numeric value has to parse as a number, sit within Min Value and Max Value, and stay inside the configured decimal places.
A payment page trims leading and trailing spaces from an entered value before checking it, so a stray space never costs the payer a submit.
Where values appear
Once captured, the values travel with the transaction:
- The transaction detail page lists each field's name and value alongside the rest of the transaction.
- Receipts include a custom fields block whenever the transaction carries any, so a payer's copy shows the order number they entered.
- The API returns the same
customFieldscollection on the transaction record, so a system that reads transactions back gets the values without a second lookup.
Clearing a field's Transaction Reports > Visible setting removes it from the first two: the detail page and the receipt stop listing it, and a transaction whose every field is hidden shows no custom fields section at all. Nothing else changes. The value is still captured, still stored on the transaction, and still returned by the API, so an integration reading transactions back is unaffected.
Note two details:
- A receipt is frozen when it's issued, so changing the setting later doesn't rewrite a receipt a payer already has.
- Hiding applies by name. A value captured under a name you have since renamed or deleted keeps showing, which is deliberate: a definition change made today should never erase a value without warning from a payment taken months ago.
Choose good field names
A few habits pay off, since the name is both an API contract and a storage key:
- Keep names stable. Renaming a definition doesn't rewrite the values already stored on past transactions, and any integration posting the old name needs updating at the same time.
- Put the human-readable wording in the description rather than the name. Names carry letters, numbers and underscores but no spaces, so
po_numberwith the description "Purchase order number" reads better on a payment page than trying to make the name itself presentable. - A few underscored names are reserved, because a payment page session already uses them to carry something else: the amount and address keys you can pre-fill a page with, such as
base_amount,customer_emailandbilling_zip, and the keys a page refuses outright, such ascard_numberandtenant_id. Saving a field under one of those names is refused, and the message says so. Prefix it to make it yours:customer_tenant_idis fine. - Retire a field by clearing Enabled rather than deleting it. Historical transactions keep their values, and you can bring the field back later. A disabled field stops being offered on payment pages, so switch any integration that still sends it over first.
Custom fields on invoices
Invoicing has its own, separate custom field definitions, managed at /Invoicing/CustomFieldDefinitions and used on invoices. They're configured independently of the transaction custom fields described here, so defining a field in one place doesn't create it in the other. If you want the same piece of data on both an invoice and a payment, define it in both.
See also
- Transaction lifecycle and settlement for what happens to the transaction your custom field values are attached to.
- Setting up a hosted payment page for building the page whose Custom Fields step scopes the fields above.