# Authorize.Net

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 2–3 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to Authorize.Net using the API Connector with body-embedded credentials — unlike most payment APIs, Authorize.Net puts authentication inside the JSON body as a `merchantAuthentication` object, not in HTTP headers. Mark the `name` (API Login ID) and `transactionKey` fields as Private in the body parameters. All operations target a single endpoint with the operation type set via `requestType` in the body.

## Authorize.Net + Bubble: Body-Based Auth and the Single-Endpoint Pattern

Authorize.Net's authentication model is unlike every other payment API you may have used. Instead of an `Authorization` header, every single request body must contain a `merchantAuthentication` object:

```json
{
  "merchantAuthentication": {
    "name": "YOUR_API_LOGIN_ID",
    "transactionKey": "YOUR_TRANSACTION_KEY"
  }
}
```

This means the 'Private' checkbox in Bubble's API Connector shared headers section is NOT the right place for Authorize.Net credentials. Instead, you mark the `name` and `transactionKey` fields as Private within the body parameters of each API call. Bubble's server-side infrastructure will inject them before sending the request, so they never appear in the browser.

The second unusual pattern: all Authorize.Net API operations target the exact same URL. There are no different endpoints for getting transactions, creating charges, or processing refunds. Instead, the operation type is specified in the body via the `requestType` field (for example, `getTransactionListRequest` or `createTransactionRequest`). In Bubble's API Connector, you create one call per operation type, each pointing to the same base URL with a different body structure.

If you are an existing Authorize.Net merchant moving an existing payment integration to Bubble, this tutorial shows the full flow from credentials to production.

## Before you start

- An active Authorize.Net merchant account. Get your API Login ID and Transaction Key from the Authorize.Net merchant portal: Account → Settings → API Credentials & Keys.
- If you need recurring billing, verify that Automated Recurring Billing (ARB) is enabled on your account.
- If you need stored customer payment profiles, verify that Customer Information Manager (CIM) is enabled — it is a paid add-on and calls to CIM endpoints will fail with `E00040` without it.
- A Bubble app on a paid plan (Starter or above) if you want to receive Silent Post / webhook IPN notifications via Backend Workflows.
- A Bubble database type for Transactions with at minimum: transaction_id (text), amount (number), status (text), card_last4 (text), card_exp (text) — required for refund processing.

## Step-by-step guide

### 1. Step 1: Get Your API Credentials from Authorize.Net

Log into the Authorize.Net merchant portal at login.authorize.net. Navigate to Account → Settings → Security Settings → API Credentials & Keys.

You need two values:
- **API Login ID**: A short identifier for your account (e.g., `5KP3u95bDbY`)
- **Transaction Key**: A longer secret key. You can generate a new one or use an existing one. If you generate a new key, note that it invalidates the previous key immediately — update any existing integrations before rotating.

For the sandbox environment, use the Authorize.Net sandbox portal (sandbox.authorize.net) and get sandbox-specific credentials. Sandbox and live credentials are completely separate.

Also note the sandbox vs live API endpoints:
- Sandbox: `https://apitest.authorize.net/xml/v1/request.api`
- Live: `https://api2.authorize.net/xml/v1/request.api`

Store both endpoints — you will use sandbox during setup and switch to live before launch.

```
{
  "sandbox": {
    "base_url": "https://apitest.authorize.net/xml/v1/request.api",
    "api_login_id": "your_sandbox_api_login_id",
    "transaction_key": "your_sandbox_transaction_key"
  },
  "live": {
    "base_url": "https://api2.authorize.net/xml/v1/request.api",
    "api_login_id": "your_live_api_login_id",
    "transaction_key": "your_live_transaction_key"
  }
}
```

**Expected result:** You have your sandbox API Login ID and Transaction Key written down and ready to enter into Bubble's API Connector.

### 2. Step 2: Install the API Connector and Configure the Body-Based Auth Pattern

In Bubble, go to Plugins tab → Add plugins → search for 'API Connector' (by Bubble) → Install. Then click 'API Connector' in the plugin list.

Click 'Add another API' and name it 'Authorize.Net'. In the Shared Headers section, add one header: `Content-Type: application/json`. Do NOT add auth credentials here — Authorize.Net auth goes in the request body, not headers.

Click 'Add a call' and name it 'Get Transaction List'. Set the method to POST. For the URL, enter the full sandbox endpoint: `https://apitest.authorize.net/xml/v1/request.api`. (Note: Authorize.Net uses a single URL for all operations — there is no separate path per operation type.)

In the body section, select JSON and enter the following structure. This is a safe read-only call that retrieves recent transactions to confirm connectivity:

```json
{
  "getTransactionListRequest": {
    "merchantAuthentication": {
      "name": "your_api_login_id",
      "transactionKey": "your_transaction_key"
    },
    "refId": "123456",
    "sorting": {
      "orderBy": "submitTimeUTC",
      "orderDescending": true
    },
    "paging": {
      "limit": 20,
      "offset": 1
    }
  }
}
```

In Bubble's parameter list (the fields Bubble extracted from the JSON), find `name` under `merchantAuthentication` and check its 'Private' checkbox. Do the same for `transactionKey`. This ensures these values are injected server-side by Bubble and never appear in browser network requests.

Change `your_api_login_id` and `your_transaction_key` to your actual sandbox values in the parameter default value fields.

```
{
  "getTransactionListRequest": {
    "merchantAuthentication": {
      "name": "<private: api_login_id>",
      "transactionKey": "<private: transaction_key>"
    },
    "refId": "<dynamic: ref_id>",
    "sorting": {
      "orderBy": "submitTimeUTC",
      "orderDescending": true
    },
    "paging": {
      "limit": 20,
      "offset": "<dynamic: offset>"
    }
  }
}
```

**Expected result:** The 'Get Transaction List' call shows `name` and `transactionKey` parameters with lock icons, confirming they are marked Private.

### 3. Step 3: Initialize the API Connector Call

With the 'Get Transaction List' call configured and your real sandbox credentials in the Private parameter fields, click 'Initialize call'. Bubble will execute the POST request against the sandbox API.

A successful response returns a JSON object like:

```json
{
  "getTransactionListResponse": {
    "messages": { "resultCode": "Ok" },
    "transactions": { "transaction": [...] },
    "totalNumInResultSet": 42
  }
}
```

Bubble's type detector will map the nested `transaction` array structure. IMPORTANT: Always initialize with a response that includes multiple transactions, not just one. Authorize.Net returns a single transaction as an object (not an array), but returns multiple transactions as an array. If you initialize with a single-transaction response, Bubble types the field as an object — and live queries with multiple results will fail silently or misalign field types. If your sandbox only has one test transaction, create a second test transaction before initializing.

After successful initialization, set 'Use as' to 'Data' (this call returns data for display).

If Initialize fails with 'There was an issue setting up your call', check:
1. The URL ends exactly at `.api` with no trailing slash
2. The `name` and `transactionKey` Private field values are correct
3. You are using sandbox credentials with the sandbox URL (not live)

```
// Expected successful response structure
{
  "getTransactionListResponse": {
    "messages": {
      "resultCode": "Ok",
      "message": [{ "code": "I00001", "text": "Successful." }]
    },
    "transactions": {
      "transaction": [
        {
          "transId": "60127327903",
          "submitTimeUTC": "2026-01-15T10:30:00",
          "transactionStatus": "settledSuccessfully",
          "invoiceNumber": "INV-001",
          "firstName": "Jane",
          "lastName": "Doe",
          "accountType": "Visa",
          "accountNumber": "XXXX1234",
          "settleAmount": 29.99
        }
      ]
    },
    "totalNumInResultSet": 42
  }
}
```

**Expected result:** Bubble shows 'Get Transaction List' with a green 'Initialized' status and detected fields including the `transaction` array with fields like transId, settleAmount, transactionStatus, accountNumber.

### 4. Step 4: Build the Transaction Display Workflow

Now that the 'Get Transaction List' call is initialized, you can bind it to a Repeating Group on a Bubble page.

On your admin page, add a Repeating Group. Set its 'Type of content' to 'Authorize.Net Get Transaction List's transaction' (the detected type). In the 'Data source' field, select 'Get data from an external API' → choose your 'Get Transaction List' call.

For the dynamic fields: set `offset` to `1` (Authorize.Net uses 1-based offset — page 1 starts at offset 1, page 2 at offset 21 if your limit is 20). Set `ref_id` to any reference string or leave as a constant.

Inside the Repeating Group cells, bind text elements to the transaction fields: `Current cell's transId`, `Current cell's settleAmount`, `Current cell's transactionStatus`, `Current cell's accountType`, `Current cell's accountNumber`.

For pagination, add a custom state on the page named `current_page` (number, default 1). A 'Next Page' button triggers a workflow:
1. Change `current_page` = `current_page + 1`
2. Refresh the Repeating Group data source with `offset = ((current_page - 1) * 20) + 1`

Note: Authorize.Net's pagination offset formula is `((page_number - 1) × limit) + 1`. Page 1 = offset 1. Page 2 = offset 21. Page 3 = offset 41.

```
// Bubble formula for Authorize.Net pagination offset (1-based)
// Custom state: current_page (number, default 1)
// In the 'offset' dynamic field of the API call:
// ((current_page - 1) * 20) + 1

// Page 1: ((1 - 1) * 20) + 1 = 1
// Page 2: ((2 - 1) * 20) + 1 = 21
// Page 3: ((3 - 1) * 20) + 1 = 41
```

**Expected result:** The Repeating Group displays transaction IDs, amounts, statuses, and card types from Authorize.Net, with a working Next Page button that correctly offsets to the next 20 results.

### 5. Step 5: Add a Refund Workflow

Processing a refund with Authorize.Net requires three pieces of data: the original transaction ID (`refTransId`), the original transaction amount, and the card's last-4 digits and expiration date. These CANNOT be fetched on demand from Authorize.Net without a separate API call — you must have stored them in your Bubble database at the time of the original transaction.

In your Bubble database, ensure your Transaction type has fields: `transaction_id`, `amount`, `card_last4`, `card_expiry` (store as text in MMYY format).

In the API Connector, add a new call named 'Refund Transaction'. Method: POST. Same URL as before. Body structure:

```json
{
  "createTransactionRequest": {
    "merchantAuthentication": {
      "name": "<private: api_login_id>",
      "transactionKey": "<private: transaction_key>"
    },
    "transactionRequest": {
      "transactionType": "refundTransaction",
      "amount": "<dynamic: refund_amount>",
      "payment": {
        "creditCard": {
          "cardNumber": "<dynamic: card_last4>",
          "expirationDate": "<dynamic: card_expiry>"
        }
      },
      "refTransId": "<dynamic: original_transaction_id>"
    }
  }
}
```

Mark `name` and `transactionKey` as Private. Initialize this call with a test refund in sandbox.

In your Bubble page workflow, add an 'Only when' condition to the Refund button: `Current cell's Transaction's status is 'settledSuccessfully'`. Authorize.Net only processes refunds on settled transactions — attempting a refund on an authorized or pending-settlement transaction returns an error. The settlement window is typically 24 hours after transaction creation.

For additional safety, add a confirmation dialog ('Do you want to refund $X?') before the refund step fires.

```
{
  "createTransactionRequest": {
    "merchantAuthentication": {
      "name": "<private: api_login_id>",
      "transactionKey": "<private: transaction_key>"
    },
    "transactionRequest": {
      "transactionType": "refundTransaction",
      "amount": "<dynamic: refund_amount>",
      "payment": {
        "creditCard": {
          "cardNumber": "<dynamic: card_last4>",
          "expirationDate": "<dynamic: card_expiry_MMYY>"
        }
      },
      "refTransId": "<dynamic: original_transaction_id>"
    }
  }
}
```

**Expected result:** Clicking Refund on a settled transaction calls Authorize.Net's refundTransaction endpoint, receives a success response, and the Bubble workflow updates the transaction status to 'refunded' in your database.

### 6. Step 6: Set Up Silent Post IPN and Switch to Live

Authorize.Net can send a Silent Post notification to your server when a transaction completes. This is useful for updating order status even if a user's browser closes before your returnUrl page loads. Silent Post uses `application/x-www-form-urlencoded` format (not JSON).

To set up the Backend Workflow endpoint (requires a paid Bubble plan):
1. Go to Settings → API → check 'This app exposes a Workflow API' → Save.
2. In Backend Workflows, create a new API workflow named `authorizenet_ipn`.
3. Check 'Detect request data'. Bubble will wait for an incoming POST.
4. In Authorize.Net's merchant portal, go to Account → Settings → Silent Post URL. Enter your Backend Workflow URL: `https://yourapp.bubbleapps.io/api/1.1/wf/authorizenet_ipn`.
5. Trigger a test transaction in sandbox. Authorize.Net will POST the form-encoded payment data to your endpoint.
6. Bubble detects the fields: `x_trans_id`, `x_amount`, `x_response_code`, `x_auth_code`, `x_type`.
7. Add workflow steps to: look up the Order by `x_invoice_num` (if you pass an invoice number in your transaction), update the Order status based on `x_response_code` (1 = Approved, 2 = Declined, 3 = Error).

To switch to live:
- Update the API Connector URL to `https://api2.authorize.net/xml/v1/request.api`
- Update the Private `name` and `transactionKey` parameter values to your live credentials
- Re-initialize each call to confirm the live response schema matches sandbox

```
// Authorize.Net Silent Post key fields (form-encoded)
// x_response_code: 1=Approved, 2=Declined, 3=Error
// x_trans_id: Authorize.Net transaction ID
// x_amount: Transaction amount
// x_auth_code: Authorization code
// x_invoice_num: Your reference ID (pass in transactionRequest.order.invoiceNumber)
// x_card_num: Last 4 digits (XXXX1234 format)
// x_exp_date: Expiry (XXXX format, masked)

// Backend Workflow endpoint:
// https://yourapp.bubbleapps.io/api/1.1/wf/authorizenet_ipn

// Content-Type sent by Authorize.Net: application/x-www-form-urlencoded
// Use Bubble's 'Detect request data' to parse form fields automatically
```

**Expected result:** Your Bubble Backend Workflow receives Authorize.Net Silent Post IPN notifications, detects the form-encoded fields, and updates your Order records based on the response code. Live credentials are active and processing real transactions.

## Best practices

- Always store transaction ID, amount, card last-4, and card expiry in your Bubble database at the time of each transaction. Authorize.Net's refund endpoint requires this data, and fetching it on demand requires an extra API call that adds latency and WU cost.
- Mark both `name` (API Login ID) and `transactionKey` as Private in every API Connector call body. Do not rely on shared headers for auth — Authorize.Net credentials must be in the body of each request.
- Always initialize the API Connector with a multi-transaction response (at least 2 test transactions in sandbox). A single-transaction initialization causes type mismatches in production where multiple transactions are returned.
- Use Authorize.Net's sandbox environment (apitest.authorize.net) for all development and testing. Never run initialize or test workflows against the live endpoint — even test transactions on a live account may incur fees.
- Set Data tab → Privacy rules for your Transaction type in Bubble so customers can only view their own records. Without privacy rules, all transactions are readable by all users — a serious security risk for financial data.
- For pagination, always use 1-based offsets: page 1 = offset 1, page 2 = offset (limit + 1). Using 0-based offsets causes either an API error or silently returns the wrong page.
- When going live, change both the API URL (to api2.authorize.net) and the Private credential values simultaneously. Mismatched URL/credential environments both return E00007, making it hard to diagnose which is wrong.
- Display Authorize.Net's refund eligibility window (180 days) in your Bubble admin UI. Operators should know that refunds on older transactions require manual Authorize.Net portal action, not an API call.

## Use cases

### Transaction Search and Reporting Dashboard

Build a Bubble back-office dashboard for Authorize.Net merchants to search, filter, and view transaction history. Use `getTransactionListRequest` with date range parameters to populate a Repeating Group with transaction amounts, statuses, and card types.

Prompt example:

```
Build a Bubble workflow that calls Authorize.Net's getTransactionListRequest with a date range filter, paginating results using the 1-based offset pattern, and displays transaction ID, amount, status, and card type in a Repeating Group.
```

### Refund Processing Workflow

Allow authorized Bubble users (operators or admins) to issue refunds on settled transactions directly from a Bubble dashboard. The refund workflow pulls stored transaction details from the Bubble database and calls `createTransactionRequest` with `transactionType: refundTransaction`.

Prompt example:

```
When an admin clicks Refund on a transaction row, trigger a Bubble workflow that checks the transaction status is Settled, confirms the refund amount, and calls createTransactionRequest with the refundTransaction type, refTransId, amount, and card last-4 from the Bubble database record.
```

### Subscription Billing Management

Businesses using Authorize.Net's Automated Recurring Billing (ARB) can use Bubble to manage subscription records, view upcoming renewals, and cancel subscriptions via the `ARBGetSubscriptionListRequest` and `ARBCancelSubscriptionRequest` operations.

Prompt example:

```
Build a Bubble page that displays all active ARB subscriptions from Authorize.Net, shows the next billing date and amount for each, and provides a Cancel button that calls ARBCancelSubscriptionRequest and updates the Bubble subscription record status.
```

## Troubleshooting

### Initialize call returns 'There was an issue setting up your call' or the response shows resultCode: Error with code E00007

Cause: E00007 means invalid merchantAuthentication — either the API Login ID or Transaction Key is wrong. This is the most common initialization error. The Private fields may have the wrong values, or you may be using live credentials against the sandbox URL (or vice versa).

Solution: In the API Connector, click the 'Get Transaction List' call and find the `name` and `transactionKey` parameters. Click each one to view/edit its value (Private values are masked in the UI but editable). Re-enter your sandbox API Login ID for `name` and your sandbox Transaction Key for `transactionKey`. Verify you are using the sandbox URL (`https://apitest.authorize.net/xml/v1/request.api`) with sandbox credentials.

### Transaction list returns one item correctly in testing, but displays no items or crashes in production with multiple transactions

Cause: Authorize.Net returns a single transaction as a JSON object but returns multiple transactions as a JSON array. If the Initialize call was run when only one transaction existed, Bubble typed the `transaction` field as an object. Live queries with multiple results return an array, causing a type mismatch.

Solution: Ensure at least 2 transactions exist in your sandbox account before re-initializing. Create a second test transaction in sandbox, then go to the API Connector → Get Transaction List → click Initialize again. Bubble will re-detect the `transaction` field as an array. Save and re-map any Repeating Group data bindings that referenced the old type.

### Refund workflow fails with error 'The referenced transaction does not meet the criteria for issuing a credit' or similar

Cause: The transaction is not yet in 'settledSuccessfully' status (still authorized or pending settlement), or the refund data (card last-4, expiry) does not match the original transaction.

Solution: Add an 'Only when' condition on the Refund workflow trigger: `Current cell's transaction status is 'settledSuccessfully'`. Settlement typically occurs within 24 hours of authorization. Also confirm that the `cardNumber` field is the last 4 digits only (not the full number) and `expirationDate` is in MMYY format exactly.

### Calls to getCustomerProfile or any CIM endpoint fail with error code E00040

Cause: E00040 means the Customer Information Manager (CIM) feature is not enabled on your Authorize.Net account. CIM is a paid add-on for storing customer payment profiles — it is not included in the base gateway subscription.

Solution: Log into the Authorize.Net merchant portal → Account → Settings → User Profile → and look for the CIM section to request activation. Alternatively, contact Authorize.Net support. Until CIM is activated, all `getCustomerProfile`, `createCustomerProfile`, and related calls will return E00040.

### Backend Workflow does not receive Silent Post notifications from Authorize.Net

Cause: Either the Bubble app is on the Free plan (Backend Workflows require paid plans), the Workflow API is not enabled in Settings, or Authorize.Net's Silent Post URL was set to the Bubble '/initialize' detection URL rather than the production workflow URL.

Solution: Go to Settings → API → confirm 'This app exposes a Workflow API' is checked. Verify your Bubble plan is Starter or above. In Authorize.Net's merchant portal, confirm the Silent Post URL is `https://yourapp.bubbleapps.io/api/1.1/wf/authorizenet_ipn` — without '/initialize'. Trigger a test transaction and check Bubble's Logs tab for incoming requests.

## Frequently asked questions

### Why can't I put the Authorize.Net credentials in the API Connector's shared header field?

Authorize.Net's API design requires credentials inside the JSON request body as a `merchantAuthentication` object — the API does not support header-based authentication. Bubble's API Connector does not have a 'body auth' mode, but you can mark individual body parameters as Private, which achieves the same security: Bubble's servers inject the values before the request leaves, so they never reach the browser.

### Why does the API Connector say 'There was an issue setting up your call' when I initialize?

This means the initialize call did not receive a successful response from Authorize.Net. Most likely the API Login ID or Transaction Key in the Private parameter fields is wrong, or you are using sandbox credentials against the live URL (or vice versa). Open Bubble's Logs tab right after the failed initialize — you will see the raw HTTP response with an Authorize.Net error code (E00007 = wrong credentials).

### Why do all my CIM calls fail with error E00040?

E00040 means the Customer Information Manager (CIM) feature is not active on your merchant account. CIM — which allows storing customer payment profiles — is a paid add-on, not included in the base Authorize.Net gateway subscription. Contact Authorize.Net support or activate it in your merchant portal under Account → Settings.

### What is the correct Authorize.Net pagination offset for page 2 with a limit of 20?

Authorize.Net uses 1-based offsets: page 1 = offset 1, page 2 = offset 21, page 3 = offset 41. The formula is `((page_number - 1) × limit) + 1`. This is different from most REST APIs that use 0-based offsets. In Bubble's formula editor: `((current_page - 1) * 20) + 1`.

### Can I use Authorize.Net on a Free Bubble plan?

Yes, you can use the API Connector (transaction queries, refunds) on the Free plan. However, receiving Silent Post / IPN webhook notifications requires Backend Workflows, which are only available on paid plans (Starter+). On the Free plan, you would need to manually poll for transaction status or rely on your returnUrl page to confirm payments.

### Do I need to store card details in my Bubble database to process refunds?

Yes. Authorize.Net's refund endpoint requires the original transaction ID, the card's last-4 digits, and the expiration date. These are not retrievable from a simple transaction list query — you need to have stored them in your Bubble database at the time of the original transaction (or make a separate `getTransactionDetailsRequest` call to retrieve them). Plan your database schema to capture these fields upfront.

---

Source: https://www.rapidevelopers.com/bubble-integrations/authorize-net
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/authorize-net
