# Test AVS responses

Drive an address match, a mismatch and an unavailable result through the sandbox, so your integration reads the AVS code off the response instead of assuming the address was checked.

4 steps, 3 API calls

**Products:** Payments, Transactions

Samples use {{API_KEY}} for your API key and {{BASE_URL}} for this instance's API address. Anything else in double braces is a value an earlier step gave you.

## Drive each response in the sandbox

### 1. Send a sale whose address verifies

API call

`POST /api/transactions`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/transactionsCreate.md)

Sending the billing ZIP 66666 makes the sandbox answer with the AVS response code "Y" and report it as "Address and ZIP match" in the response. This is the baseline. Run it first so the two below are a comparison rather than a single result you have nothing to weigh against.

cURL:

```bash
curl -X POST "{{BASE_URL}}/api/transactions" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "cardData": {
      "cardNumber": "4111111111111111",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "billingAddress": { "zip": "66666" }
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
```

.NET:

```csharp
using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");

var response = await http.PostAsJsonAsync("/api/transactions", new
{
    transactionType = "Sale",
    cardData = new
    {
        cardNumber = "4111111111111111",
        nameOnCard = "Jane Doe",
        expirationMonth = 12,
        expirationYear = 2030,
        billingAddress = new { zip = "66666" }
    },
    invoiceData = new
    {
        amounts = new { @base = 10.00m, total = 10.00m }
    }
});

var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var validation = result.GetProperty("responseData").GetProperty("cardValidationData");
var avs = validation.GetProperty("avsResponse").GetString();
```

**What this step answers with** (HTTP 200)

Abridged to the properties this step depends on. A real response carries more.

```json
{
  "id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
  "resultCode": "Ok",
  "authorizedAmount": 10.00,
  "responseData": {
    "resultCode": "Ok",
    "resultMessage": "Approved",
    "cardValidationData": {
      "avsResponse": "Y",
      "avsResponseText": "Address and ZIP match"
    }
  }
}
```

### 2. Send a sale whose address fails to verify

API call

`POST /api/transactions`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/transactionsCreate.md)

Sending the billing ZIP 33333 makes the sandbox answer with the AVS response code "N" and report it as "Address and ZIP do not match" in the response. The transaction still approves. AVS doesn't refuse anything on its own, so an integration that reads only the result code can't tell this response apart from the one above.

cURL:

```bash
curl -X POST "{{BASE_URL}}/api/transactions" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "cardData": {
      "cardNumber": "4111111111111111",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "billingAddress": { "zip": "33333" }
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
```

.NET:

```csharp
using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");

var response = await http.PostAsJsonAsync("/api/transactions", new
{
    transactionType = "Sale",
    cardData = new
    {
        cardNumber = "4111111111111111",
        nameOnCard = "Jane Doe",
        expirationMonth = 12,
        expirationYear = 2030,
        billingAddress = new { zip = "33333" }
    },
    invoiceData = new
    {
        amounts = new { @base = 10.00m, total = 10.00m }
    }
});

var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var validation = result.GetProperty("responseData").GetProperty("cardValidationData");
var avs = validation.GetProperty("avsResponse").GetString();
```

**What this step answers with** (HTTP 200)

Abridged to the properties this step depends on. A real response carries more.

```json
{
  "id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
  "resultCode": "Ok",
  "authorizedAmount": 10.00,
  "responseData": {
    "resultCode": "Ok",
    "resultMessage": "Approved",
    "cardValidationData": {
      "avsResponse": "N",
      "avsResponseText": "Address and ZIP do not match"
    }
  }
}
```

### 3. Send a sale the issuer can't verify

API call

`POST /api/transactions`

[Reference for this operation](https://devportal-simpay-sbx.winkpg.io/docs/api/transactionsCreate.md)

Sending the billing ZIP 55555 makes the sandbox answer with the AVS response code "U" and report it as "Address information unavailable" in the response. The third state, and the one most often missed. No answer isn't the same as a mismatch, and a rule that treats it as one refuses good cardholders whose issuer doesn't participate.

cURL:

```bash
curl -X POST "{{BASE_URL}}/api/transactions" \
  -H "api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "cardData": {
      "cardNumber": "4111111111111111",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "billingAddress": { "zip": "55555" }
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
```

.NET:

```csharp
using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };
http.DefaultRequestHeaders.Add("api-key", "{{API_KEY}}");

var response = await http.PostAsJsonAsync("/api/transactions", new
{
    transactionType = "Sale",
    cardData = new
    {
        cardNumber = "4111111111111111",
        nameOnCard = "Jane Doe",
        expirationMonth = 12,
        expirationYear = 2030,
        billingAddress = new { zip = "55555" }
    },
    invoiceData = new
    {
        amounts = new { @base = 10.00m, total = 10.00m }
    }
});

var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var validation = result.GetProperty("responseData").GetProperty("cardValidationData");
var avs = validation.GetProperty("avsResponse").GetString();
```

**What this step answers with** (HTTP 200)

Abridged to the properties this step depends on. A real response carries more.

```json
{
  "id": "9f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "merchantId": "3a7b1c9d-2e4f-4a6b-8c8d-9e0f1a2b3c4d",
  "resultCode": "Ok",
  "authorizedAmount": 10.00,
  "responseData": {
    "resultCode": "Ok",
    "resultMessage": "Approved",
    "cardValidationData": {
      "avsResponse": "U",
      "avsResponseText": "Address information unavailable"
    }
  }
}
```

## Read the verification codes

### 4. Compare the three responses

On your side

The code is on responseData.cardValidationData.avsResponse and the text the simulator reported it with is on responseData.cardValidationData.avsResponseText. Every call above sent the sandbox's guaranteed-approval amount, so the result code was the same on all three and only the verification code moved. The sandbox honours 17 billing ZIP triggers in total. The full table is on the testing page rather than repeated here. The verification code never changes the result code, so all three of these approve. Whether a mismatch should refuse the payment is the merchant's policy, configured on the merchant's card verification settings and applied after the processor answers. What your integration owes the payer is a decision it can explain. Read the code, make the call on purpose, and treat an unavailable answer as its own case rather than folding it into the mismatch branch.

[Testing your integration](https://devportal-simpay-sbx.winkpg.io/docs/testing.md)

## Run the whole flow as one script

Every step above in one script you can copy and run. Replace {{API_KEY}} with your own API key and {{BASE_URL}} with this instance's API address, and set any value the script asks you for at the top. A step that happens outside the API stays a comment.

### cURL

```bash
brew install jq
```

```bash
#!/usr/bin/env bash
# Test AVS responses
#
# Drive an address match, a mismatch and an unavailable result through the sandbox, so your
# integration reads the AVS code off the response instead of assuming the address was checked.
#
# Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
# it. A step that happens outside the API is a comment, and any failed call stops the script.

set -euo pipefail

BASE_URL="{{BASE_URL}}"
API_KEY="{{API_KEY}}"

# Sends one request and prints the response body. A failed call prints the API's answer and stops
# the script.
call() {
  local method="$1" path="$2" body="${3:-}" out
  local args=(-sS --fail-with-body -X "$method" "$BASE_URL$path" -H "api-key: $API_KEY")
  if [ -n "$body" ]; then
    args+=(-H "Content-Type: application/json" -d "$body")
  fi
  if ! out=$(curl "${args[@]}"); then
    printf '%s\n' "$out" >&2
    return 1
  fi
  printf '%s' "$out"
}

# Phase 1: Drive each response in the sandbox

# Step 1: Send a sale whose address verifies
call POST "/api/transactions" '{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "66666" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}' > /dev/null

# Step 2: Send a sale whose address fails to verify
call POST "/api/transactions" '{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "33333" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}' > /dev/null

# Step 3: Send a sale the issuer can't verify
call POST "/api/transactions" '{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "55555" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}' > /dev/null

# Phase 2: Read the verification codes

# Step 4: Compare the three responses
# The code is on responseData.cardValidationData.avsResponse and the text the simulator reported it
# with is on responseData.cardValidationData.avsResponseText. Every call above sent the sandbox's
# guaranteed-approval amount, so the result code was the same on all three and only the verification
# code moved. The sandbox honours 17 billing ZIP triggers in total. The full table is on the testing
# page rather than repeated here. The verification code never changes the result code, so all three
# of these approve. Whether a mismatch should refuse the payment is the merchant's policy,
# configured on the merchant's card verification settings and applied after the processor answers.
# What your integration owes the payer is a decision it can explain. Read the code, make the call on
# purpose, and treat an unavailable answer as its own case rather than folding it into the mismatch
# branch.
```

### PowerShell

```powershell
# Test AVS responses
#
# Drive an address match, a mismatch and an unavailable result through the sandbox, so your
# integration reads the AVS code off the response instead of assuming the address was checked.
#
# Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
# it. A step that happens outside the API is a comment, and any failed call stops the script.

$ErrorActionPreference = 'Stop'

$baseUrl = '{{BASE_URL}}'
$apiKey = '{{API_KEY}}'

# Sends one request and returns the parsed response body. A failed call stops the script.
function Invoke-BlueprintCall([string] $Method, [string] $Path, [string] $Body) {
    $arguments = @{
        Method  = $Method
        Uri     = $baseUrl + $Path
        Headers = @{ 'api-key' = $apiKey }
    }
    if ($Body) {
        $arguments.ContentType = 'application/json'
        $arguments.Body = [System.Text.Encoding]::UTF8.GetBytes($Body)
    }
    Invoke-RestMethod @arguments
}

# Phase 1: Drive each response in the sandbox

# Step 1: Send a sale whose address verifies
$body = @'
{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "66666" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}
'@
$null = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body

# Step 2: Send a sale whose address fails to verify
$body = @'
{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "33333" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}
'@
$null = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body

# Step 3: Send a sale the issuer can't verify
$body = @'
{
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "55555" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
}
'@
$null = Invoke-BlueprintCall -Method 'POST' -Path '/api/transactions' -Body $body

# Phase 2: Read the verification codes

# Step 4: Compare the three responses
# The code is on responseData.cardValidationData.avsResponse and the text the simulator reported it
# with is on responseData.cardValidationData.avsResponseText. Every call above sent the sandbox's
# guaranteed-approval amount, so the result code was the same on all three and only the verification
# code moved. The sandbox honours 17 billing ZIP triggers in total. The full table is on the testing
# page rather than repeated here. The verification code never changes the result code, so all three
# of these approve. Whether a mismatch should refuse the payment is the merchant's policy,
# configured on the merchant's card verification settings and applied after the processor answers.
# What your integration owes the payer is a decision it can explain. Read the code, make the call on
# purpose, and treat an unavailable answer as its own case rather than folding it into the mismatch
# branch.
```

### TypeScript

```typescript
// Test AVS responses
//
// Drive an address match, a mismatch and an unavailable result through the sandbox, so your
// integration reads the AVS code off the response instead of assuming the address was checked.
//
// Every API call in this blueprint, in order. Each value a call returns is passed to the calls
// after it. A step that happens outside the API is a comment, and any failed call stops the script.

export {};

const baseUrl = '{{BASE_URL}}';
const apiKey = '{{API_KEY}}';

// Sends one request and returns the parsed response body. A failed call throws.
async function call(method: string, path: string, body?: unknown): Promise<any> {
  const headers: Record<string, string> = { 'api-key': apiKey };
  if (body !== undefined) {
    headers['Content-Type'] = 'application/json';
  }

  const response = await fetch(baseUrl + path, {
    method,
    headers,
    body: body === undefined ? undefined : JSON.stringify(body),
  });

  const text = await response.text();
  if (!response.ok) {
    throw new Error(`${method} ${path} answered ${response.status}: ${text}`);
  }

  return text ? JSON.parse(text) : null;
}

// Phase 1: Drive each response in the sandbox

// Step 1: Send a sale whose address verifies
await call('POST', '/api/transactions', {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "66666" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
});

// Step 2: Send a sale whose address fails to verify
await call('POST', '/api/transactions', {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "33333" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
});

// Step 3: Send a sale the issuer can't verify
await call('POST', '/api/transactions', {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "55555" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
});

// Phase 2: Read the verification codes

// Step 4: Compare the three responses
// The code is on responseData.cardValidationData.avsResponse and the text the simulator reported it
// with is on responseData.cardValidationData.avsResponseText. Every call above sent the sandbox's
// guaranteed-approval amount, so the result code was the same on all three and only the
// verification code moved. The sandbox honours 17 billing ZIP triggers in total. The full table is
// on the testing page rather than repeated here. The verification code never changes the result
// code, so all three of these approve. Whether a mismatch should refuse the payment is the
// merchant's policy, configured on the merchant's card verification settings and applied after the
// processor answers. What your integration owes the payer is a decision it can explain. Read the
// code, make the call on purpose, and treat an unavailable answer as its own case rather than
// folding it into the mismatch branch.
```

### C#

```csharp
// Test AVS responses
//
// Drive an address match, a mismatch and an unavailable result through the sandbox, so your
// integration reads the AVS code off the response instead of assuming the address was checked.
//
// Every API call in this blueprint, in order. Each value a call returns is passed to the calls
// after it. A step that happens outside the API is a comment, and any failed call stops the script.

using System.Text;
using System.Text.Json;

var baseUrl = "{{BASE_URL}}";
var apiKey = "{{API_KEY}}";

using var http = new HttpClient();
http.DefaultRequestHeaders.Add("api-key", apiKey);

// Sends one request and returns the parsed response body. A failed call throws.
async Task<JsonElement> CallAsync(string method, string path, string? body = null)
{
    using var request = new HttpRequestMessage(new HttpMethod(method), baseUrl + path);
    if (body is not null)
    {
        request.Content = new StringContent(body, Encoding.UTF8, "application/json");
    }

    using var response = await http.SendAsync(request);
    var json = await response.Content.ReadAsStringAsync();
    if (!response.IsSuccessStatusCode)
    {
        throw new HttpRequestException($"{method} {path} answered {(int)response.StatusCode}: {json}");
    }

    return json.Length == 0 ? default : JsonSerializer.Deserialize<JsonElement>(json);
}

// Phase 1: Drive each response in the sandbox

// Step 1: Send a sale whose address verifies
await CallAsync("POST", "/api/transactions", """
    {
      "transactionType": "Sale",
      "cardData": {
        "cardNumber": "4111111111111111",
        "nameOnCard": "Jane Doe",
        "expirationMonth": 12,
        "expirationYear": 2030,
        "billingAddress": { "zip": "66666" }
      },
      "invoiceData": {
        "amounts": { "base": 10.00, "total": 10.00 }
      }
    }
    """);

// Step 2: Send a sale whose address fails to verify
await CallAsync("POST", "/api/transactions", """
    {
      "transactionType": "Sale",
      "cardData": {
        "cardNumber": "4111111111111111",
        "nameOnCard": "Jane Doe",
        "expirationMonth": 12,
        "expirationYear": 2030,
        "billingAddress": { "zip": "33333" }
      },
      "invoiceData": {
        "amounts": { "base": 10.00, "total": 10.00 }
      }
    }
    """);

// Step 3: Send a sale the issuer can't verify
await CallAsync("POST", "/api/transactions", """
    {
      "transactionType": "Sale",
      "cardData": {
        "cardNumber": "4111111111111111",
        "nameOnCard": "Jane Doe",
        "expirationMonth": 12,
        "expirationYear": 2030,
        "billingAddress": { "zip": "55555" }
      },
      "invoiceData": {
        "amounts": { "base": 10.00, "total": 10.00 }
      }
    }
    """);

// Phase 2: Read the verification codes

// Step 4: Compare the three responses
// The code is on responseData.cardValidationData.avsResponse and the text the simulator reported it
// with is on responseData.cardValidationData.avsResponseText. Every call above sent the sandbox's
// guaranteed-approval amount, so the result code was the same on all three and only the
// verification code moved. The sandbox honours 17 billing ZIP triggers in total. The full table is
// on the testing page rather than repeated here. The verification code never changes the result
// code, so all three of these approve. Whether a mismatch should refuse the payment is the
// merchant's policy, configured on the merchant's card verification settings and applied after the
// processor answers. What your integration owes the payer is a decision it can explain. Read the
// code, make the call on purpose, and treat an unavailable answer as its own case rather than
// folding it into the mismatch branch.
```

### Python

```bash
pip install requests
```

```python
# Test AVS responses
#
# Drive an address match, a mismatch and an unavailable result through the sandbox, so your
# integration reads the AVS code off the response instead of assuming the address was checked.
#
# Every API call in this blueprint, in order. Each value a call returns is passed to the calls after
# it. A step that happens outside the API is a comment, and any failed call stops the script.

import requests

BASE_URL = "{{BASE_URL}}"
API_KEY = "{{API_KEY}}"


# Sends one request and returns the parsed response body. A failed call raises.
def call(method, path, body=None):
    response = requests.request(
        method,
        BASE_URL + path,
        headers={"api-key": API_KEY},
        json=body,
    )
    response.raise_for_status()
    return response.json() if response.content else None


# Phase 1: Drive each response in the sandbox

# Step 1: Send a sale whose address verifies
call("POST", "/api/transactions", {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "66666" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
})

# Step 2: Send a sale whose address fails to verify
call("POST", "/api/transactions", {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "33333" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
})

# Step 3: Send a sale the issuer can't verify
call("POST", "/api/transactions", {
  "transactionType": "Sale",
  "cardData": {
    "cardNumber": "4111111111111111",
    "nameOnCard": "Jane Doe",
    "expirationMonth": 12,
    "expirationYear": 2030,
    "billingAddress": { "zip": "55555" }
  },
  "invoiceData": {
    "amounts": { "base": 10.00, "total": 10.00 }
  }
})

# Phase 2: Read the verification codes

# Step 4: Compare the three responses
# The code is on responseData.cardValidationData.avsResponse and the text the simulator reported it
# with is on responseData.cardValidationData.avsResponseText. Every call above sent the sandbox's
# guaranteed-approval amount, so the result code was the same on all three and only the verification
# code moved. The sandbox honours 17 billing ZIP triggers in total. The full table is on the testing
# page rather than repeated here. The verification code never changes the result code, so all three
# of these approve. Whether a mismatch should refuse the payment is the merchant's policy,
# configured on the merchant's card verification settings and applied after the processor answers.
# What your integration owes the payer is a decision it can explain. Read the code, make the call on
# purpose, and treat an unavailable answer as its own case rather than folding it into the mismatch
# branch.
```

- [Blueprints](https://devportal-simpay-sbx.winkpg.io/docs/blueprints.md): every blueprint this instance publishes.

## See also

- [All documentation](https://devportal-simpay-sbx.winkpg.io/llms.txt): the machine-readable index of every public page on this site.
