Address Verification (AVS)
Address Verification Service (AVS) checks the billing address a buyer enters against the address the card issuer has on file. Sending it with a payment is optional, but recommended for card-not-present payments:
- Higher approval rates. Issuers are more likely to approve a charge that includes a matching billing address.
- A fraud signal. A street or postal-code mismatch is a useful input to your own risk rules.
- Better interchange. For many card-not-present programs, submitting AVS data is a condition of qualifying for the best interchange rate.
Vonpay collects the billing address in the payment fields, attaches it to the payment behind the scenes, and returns a normalized result you can read back. You never handle the address yourself — it is encrypted at rest and never returned in API responses, webhooks, or logs.
AVS activates per gateway and is opt-in: a payment only carries a billing address
when your integration collects one. Integrations that don't collect a billing
address are unaffected. Requires @vonpay/vora-js 1.9.2+ for the embedded
fields. If your account's gateway hasn't enabled AVS yet, avs_result_code comes
back null and nothing else changes.
Collect the billing address
How you collect it depends on your integration mode
(integrationMode on session create).
Embedded fields — default ("embed")
In the default embed mode the billing address is collected inside the card field — there's nothing extra to mount. When the buyer completes it, it rides along with the payment automatically.
But by default they don't have to. That's the setting that decides whether you actually get AVS data.
Decide whether the address is optional or required
billingAddress on session create controls the billing section. It applies to
hosted checkout and to embedded fields alike:
| Value | What the buyer sees | Use it when |
|---|---|---|
"auto" (default) | Section shown, optional — the buyer can leave it blank and still pay | You want AVS when offered freely, and won't add friction to get it |
"required" | Buyer must complete it before the pay button enables | You want AVS data reliably |
"none" | Section hidden entirely | You never want a billing address — no AVS result |
POST /v1/sessions
{
"amount": 4999,
"currency": "USD",
"billingAddress": "required"
}
"required"On the default "auto", a buyer can skip the billing address and pay anyway —
so a share of your payments will carry no address, and avs_result_code comes
back null for those. That's not a failure, it's the setting working as
designed.
Everything on this page — better approval rates, the fraud signal, interchange
qualification — depends on the address actually being there. If you're reading
this page, "required" is probably what you want.
The trade-off is real: a required field adds checkout friction and can cost conversions. Choose deliberately rather than leaving the default by accident.
Two related fields sit alongside it on the same call. phone uses the same
none / auto / required vocabulary (default none), and collectEmail
is a boolean (default false) that shows an email input even when you already
supplied a buyer email — an email input appears automatically whenever none is
on file, flag or not. Neither affects AVS; they're listed here so the collection
controls are in one place. Full definitions in the create-session
reference.
collectPhone is retiredIt's now phone: "auto" (or "required"). The old flag is refused, not
ignored — see the migration
table.
Embedded fields — Elements ("elements")
In elements mode you place fields individually. Mount a billing address
element alongside the card:
const elements = vora.elements.create();
const card = elements.create('card');
const address = elements.create('address', { mode: 'billing' });
card.mount('#card');
address.mount('#billing-address');
// On submit, the address is collected and sent with the card automatically.
const result = await elements.submit();
No extra wiring is required — elements.submit() gathers the address and forwards
it with the token registration. See Custom checkout (Elements mode)
for the full field layout.
Payments API (server to server)
If you vault or charge a card directly through the API, include a
billing_address object on the request:
{
"amount": 1999,
"currency": "USD",
"payment_method": { "id": "vp_pmt_live_…" },
"billing_address": {
"address_line1": "1600 Pennsylvania Ave NW",
"city": "Washington",
"state": "DC",
"postal_code": "20500",
"country": "US"
}
}
Which fields to send
| Field | Required | Notes |
|---|---|---|
address_line1 | Yes | Street address. Drives the street-match portion of AVS. |
postal_code | Yes | ZIP / postal code. Drives the postal-match portion of AVS. |
country | Yes | Uppercase ISO 3166-1 alpha-2 (US, GB, …). |
city | Recommended | Improves results with issuers that verify the full address. |
state | Recommended | ISO 3166-2 subdivision for the US (e.g. CA); free text elsewhere. |
address_line2 | Optional | Apartment, suite, unit, etc. |
address_line1, postal_code, and country are the fields AVS actually checks —
send at least those. Including city and state improves the result with issuers
that verify the full address.
International
By default the embedded fields initialize for the United States. To collect AVS for buyers in other countries, set the buyer's country when you create the session:
{ "amount": 1999, "currency": "USD", "country": "GB" }
The address field then defaults to that country, and the buyer can still pick
another. Always send country in the billing address itself — that's the value
AVS verifies against.
Read the result
After a charge, the Payment Intent carries two normalized result fields:
| Field | Type | Description |
|---|---|---|
avs_result_code | string | null | match, partial_postal (postal matched, street didn't), partial_address (street matched, postal didn't), no_match, unavailable (issuer returned nothing), or not_supported. null when no billing address was sent or no result was returned. |
cvv_result_code | string | null | match, no_match, not_provided, or unavailable. |
The same two fields appear on the charge.succeeded and charge.failed
webhook events, so you can record or act on the
result server-side.
{
"id": "vpi_live_…",
"status": "succeeded",
"amount": 1999,
"currency": "USD",
"card": { "brand": "visa", "last4": "4242" },
"avs_result_code": "match",
"cvv_result_code": "match"
}
The codes are normalized and gateway-independent — the same values regardless of which processor handled the charge. Raw processor response codes are not exposed through the API.
Vonpay does not decline a payment on an AVS mismatch — a no_match charge can
still succeed. Use avs_result_code / cvv_result_code in your own risk rules to
decide whether to fulfil, review, or refund.
Adopting it
The billing-address forward is additive and opt-in. Existing integrations are unaffected — nothing is sent until you collect an address. To adopt:
- Upgrade
@vonpay/vora-jsto 1.9.2 or later (pinned URL or the auto-update channel). - Collect the billing address (the card field already does in
embedmode; mount anaddresselement inelementsmode). - Read
avs_result_codefrom the payment intent or yourcharge.*webhook.