---
name: authorize-net-salesforce
description: Build a production-grade Authorize.Net to Salesforce integration covering architecture, the step-by-step build with Apex code, field mapping, API and governor limits, security, monitoring, and the pitfalls to design out. Use when connecting, building, or debugging a Authorize.Net and Salesforce integration.
---

# Authorize.Net to Salesforce integration

> Cloudsheer's delivery playbook for Authorize.Net on Salesforce, distilled from 6 client projects and 34 delivery tasks. Apply it as the reference approach when building this integration.

## Overview

A custom payment gateway for Salesforce B2B Commerce checkout. We have shipped it across 7 client projects and 34 build tasks.

The value is what happens after the charge: matching payments to records, handling refunds, and keeping finance reconciled without manual work.

We build a hardened webhook pipeline: a public Apex REST endpoint on a Salesforce Site, signature verification on every event, and flows that turn raw Authorize.Net events into clean, reconciled records.

Every Authorize.Net build is delivered by a senior Salesforce architect on a fixed price, tested end to end in a sandbox, deployed to your org, and backed by 30 days of hypercare. You own the result: documented, source-controlled, and free of black-box middleware lock-in.

## Integration facts

**Connects via:**
- Custom Apex adapter implementing the CommercePayments.PaymentGatewayAdapter interface (processRequest)
- Outbound callout to Authorize.Net via a Named Credential
- Authorize.Net CIM customer/payment profiles for stored cards outside the paved checkout

**Package:** Custom build (no managed package)

**Authentication:** Authorize.Net merchantAuthentication = API Login ID + Transaction Key, stored in a Named Credential or protected custom setting; PAN never stored in Salesforce

**API type:** REST/XML

**API base:** `https://api.authorize.net/xml/v1/request.api (sandbox: https://apitest.authorize.net/xml/v1/request.api)`

**Key endpoints:**
- `createTransactionRequest (authOnly / authCapture / priorAuthCapture / refund)`
- `createCustomerProfileRequest (CIM)`
- `createCustomerPaymentProfileRequest (CIM)`
- `SF CommercePayments: AuthorizationRequest, CaptureRequest, ReferencedRefundRequest, TokenizeRequest`

**Webhook and platform events:**
- `net.authorize.payment.authcapture.created`
- `net.authorize.payment.authorization.created`
- `net.authorize.payment.refund.created`
- `net.authorize.payment.void.created`

**Official docs:** https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_interface_commercepayments_PaymentGatewayAdapter.htm

## Prerequisites

- A Salesforce edition with API access (Enterprise, Unlimited, or Developer)
- A dedicated sandbox to build and test in
- Authorize.Net test-mode credentials to validate before going live
- A Salesforce Site to host the public webhook endpoint
- A Authorize.Net account on a plan with API access
- System Administrator access on both systems
- A dedicated integration user with a minimum-access permission set
- Agreement on the objects, fields, and sync direction for the Authorize.Net data

## Architecture

Data flows left to right through four lanes:

1. **Sources:** Authorize.Net checkout, Authorize.Net events, Custom Apex, CommercePayments
2. **Integration layer:** Webhook / API handler, Payment flows, Reconciliation jobs
3. **Salesforce:** Payment, Related records, Reports
4. **Outcomes:** Payments reconciled, Records created, No manual entry

## How it works at runtime

1. **Customer pays** `[In Authorize.Net]`: A customer or donor pays through Authorize.Net using checkout, a payment link, or a saved card.
   - Note: `Handled entirely vendor-side, so raw card data never touches Salesforce and PCI scope stays low.`
2. **Event is emitted** `[In transit]`: Authorize.Net emits an event such as checkout.session.completed or charge.succeeded.
   - Note: `POSTed over Custom Apex, CommercePayments to a public Apex REST endpoint exposed on a Salesforce Site.`
3. **Verified and queued** `[In Salesforce]`: The endpoint verifies the signature, returns HTTP 200 immediately, and hands the work off.
   - Note: `HMAC signature checked; heavy processing runs in a Queueable so the webhook never hits its timeout.`
4. **Record is written** `[In Salesforce]`: The event is upserted to an Events object, then mapped onto the Payment and Account.
   - Note: `Idempotent upsert on the event id; a record-triggered flow maps fields and reconciles.`
5. **Reconciled and reported** `[In Salesforce]`: The payment is matched to the right record; refunds and disputes flow back automatically.
   - Note: `Amounts reconciled against invoices, with exceptions raised on a dashboard.`

## Step-by-step build

### Step 1: Plan the integration and prerequisites

We agree the events that matter and design a secure public endpoint before touching code.

- A Salesforce edition with API access, a dedicated sandbox, and a Salesforce Site for the public URL
- A Authorize.Net account in test mode first, with admin access on both systems
- The exact events, target objects, and reconciliation rules agreed up front
- A hardened Site guest user with the absolute minimum permissions

### Step 2: Build the Apex REST endpoint

We give Authorize.Net a typed, testable place to POST events.

- An @RestResource class with an @HttpPost handler mapped to a stable URL
- The raw request body is read once and kept for signature verification and audit

Reference implementation (`snippets/InboundWebhookResource.cls`):

```apex
@RestResource(urlMapping='/webhook/*')
global with sharing class InboundWebhookResource {
  @HttpPost
  global static void handle() {
    RestRequest req = RestContext.request;
    String raw = req.requestBody.toString();

    if (!WebhookSignature.isValid(raw, req.headers.get('X-Signature'))) {
      RestContext.response.statusCode = 401;      // reject unverified events
      return;
    }
    Inbound_Event__c e = new Inbound_Event__c(
      Event_Id__c = EventParser.idOf(raw), Payload__c = raw);
    upsert e Event_Id__c;                         // idempotent capture
    System.enqueueJob(new EventProcessor(e.Id));  // process asynchronously
    RestContext.response.statusCode = 200;         // respond fast
  }
}
```

### Step 3: Expose it securely on a Salesforce Site

We make the endpoint reachable without opening the whole org.

- Create a Salesforce Site and enable only the one Apex class for the guest user
- The public URL follows /services/apexrest/...; every other guest permission stays off

> **Watch out: lock down the guest user** A Salesforce Site runs as a guest user. Grant it access to only the one Apex class, or you expose far more of the org than a webhook ever should.

### Step 4: Register the webhook in Authorize.Net

We subscribe to exactly the events we need, nothing more.

- In the Authorize.Net dashboard, add the endpoint URL and select the relevant events (checkout, charge, invoice, refund, and so on)
- Copy the webhook signing secret into a protected custom setting or custom metadata

### Step 5: Verify signatures and prevent replay

We make sure only genuine, once-only events ever change data.

- Compute an HMAC-SHA256 of the raw body with the signing secret and constant-time compare it
- Reject on mismatch, and check the event timestamp to block replay attacks

Reference implementation (`snippets/WebhookSignature.cls`):

```apex
public class WebhookSignature {
  public static Boolean isValid(String rawBody, String header) {
    Blob secret = Blob.valueOf(WebhookConfig.signingSecret());
    Blob mac = Crypto.generateMac('HmacSHA256', Blob.valueOf(rawBody), secret);
    String expected = EncodingUtil.convertToHex(mac);
    // constant-time compare guards against timing attacks
    return ConstantTime.equals(expected, header);
  }
}
```

> **Watch out: verify every event** A public endpoint is a target. Validate the signing secret and make the handler idempotent, or a retried or spoofed event can double-post to your records.

### Step 6: Respond fast, process asynchronously

We never let processing block the webhook response.

- Return HTTP 200 within the vendor timeout, which is usually only a few seconds
- Hand the heavy work to a Queueable so slow processing never triggers a retry storm

Reference implementation (`snippets/EventProcessor.cls`):

```apex
public class EventProcessor implements Queueable {
  private Id eventId;
  public EventProcessor(Id eventId) { this.eventId = eventId; }

  public void execute(QueueableContext ctx) {
    Inbound_Event__c e = [SELECT Payload__c FROM Inbound_Event__c WHERE Id = :eventId];
    Map<String,Object> body = (Map<String,Object>) JSON.deserializeUntyped(e.Payload__c);
    // heavy work runs here, off the webhook thread: map onto Accounts, Cases, Payments
    EventRouter.route(body);
    update new Inbound_Event__c(Id = eventId, Processed__c = true, Processed_At__c = System.now());
  }
}
```

### Step 7: Capture raw events idempotently

We keep a durable, replayable record of everything received.

- Upsert an Inbound_Event__c on the event id so duplicate deliveries are ignored
- Store the raw JSON for audit and for replay if a downstream mapping ever changes

### Step 8: Map events onto your Payment

We turn raw Authorize.Net events into clean Salesforce data.

- Flows or triggers translate events onto Accounts, Payment, Payments, and Cases
- Store the Authorize.Net ids on the records and handle out-of-order events gracefully

### Step 9: Handle money and edge cases

We cover the cases that otherwise become disputes.

- Reconcile payments, and handle refunds, chargebacks, and partial captures
- Backfill missing data and alert on any mismatch before it reaches finance

### Step 10: Test, deploy, and monitor

We prove it end to end and keep watch in production.

- Apex tests build a RestContext request and assert the resulting records; replay real test-mode events
- Deploy via change sets, restrict the events object to admins, and monitor with error logging plus 30 days of support

## Data model

| Object | Purpose | Key fields |
| --- | --- | --- |
| `Payment` | The primary Salesforce record Authorize.Net data maps onto. | `External_Id__c, Name, Status` |
| `Inbound_Event__c (custom)` | Stores each raw event idempotently for audit and replay. | `Event_Id__c, Payload__c, Processed__c` |
| `Account` | Matched or created for the customer or company behind the record. | `Name, External_Id__c` |
| `Error_Log__c (custom)` | Captures every request, response, and failure so anything can be replayed. | `Payload__c, Status__c, Related_Id__c` |

Salesforce objects typically in play: `PaymentGateway`, `PaymentGatewayLog`, `PaymentAuthorization`, `Payment`, `CardPaymentMethod`

## Field mapping (example)

| Authorize.Net | Salesforce | Notes |
| --- | --- | --- |
| Authorize.Net charge id | `Payment.External_Id__c` | Unique external id, upsert key |
| Authorize.Net amount | `Payment.Amount` |  |
| Authorize.Net currency | `Payment.CurrencyIsoCode` |  |
| Authorize.Net customer | `Account` | Matched or created |
| Authorize.Net status | `Payment.Status` | Picklist value mapping |
| Created / updated at | `LastModifiedDate` | Enables delta sync and audit |
| Owner or rep | `Payment.OwnerId` | Assignment rules or a default owner |

Tailor the full mapping to the org. Always upsert on an external-id field so retries are idempotent.

## API and rate limits

### Authorize.Net-specific

- Salesforce Apex callout limits (100 callouts/transaction, 120s, max 10 concurrent long-running requests)
- B2B Commerce paved flow supports only CardPaymentMethod + single-use tokenization (no saved methods in checkout)
- Authorize.Net per-account transaction and velocity limits

### Salesforce platform

- A webhook handler must return within Authorize.Net's timeout, usually a few seconds. We acknowledge with HTTP 200 immediately and process the event asynchronously.
- Salesforce Sites have their own request limits. Heavy processing is offloaded to a Queueable so the public endpoint stays fast.
- Authorize.Net retries failed deliveries automatically. Idempotency on the event id means a retried event is never processed twice.

## Security checklist

- Secrets stored in Named Credentials and permission sets, never in code or metadata
- A least-privilege integration user, with field-level security and sharing scoped tight
- All traffic over TLS, with signature verification on inbound events
- Card data never touches Salesforce, keeping your PCI scope minimal
- Shield Platform Encryption available for sensitive fields
- A full audit trail: every request and response logged for traceability
- Every automation runs as a dedicated integration user, so actions are attributable and revocable
- Sandbox-first delivery and change-set deployment keep production changes reviewed and controlled

## Monitoring and reliability

- Every request and response is logged to a custom Error Log object, tagged with the related record id.
- Failed calls retry with exponential backoff; anything still failing lands in a dead-letter queue for review.
- Idempotency keys guarantee a retried or duplicate event never double-posts a record.
- A dashboard surfaces failures, latency, and volume so problems are caught before users notice.
- Optional email or Slack alerts fire on repeated failures or a stalled sync.

## Testing and deployment

- Apex unit tests with HttpCalloutMock cover the success path, failure handling, and a 200-record bulk case, at 75 percent or higher coverage.
- The full flow is validated in a sandbox against real sample data and the edge cases that matter.
- A parallel run reconciles the integration against your live system before cutover.
- Everything deploys through change sets or an SFDX and CI pipeline, under version control.
- Permission sets, sharing, and Named Credentials are configured in production, then we run 30 days of monitored hypercare.

## Pitfalls to design out

- **Missed or duplicated events:** Verify signatures, upsert on the event id, and return 200 within the timeout.
- **Webhook times out on heavy processing:** Acknowledge fast and process the event in a Queueable.
- **Guest user exposes too much:** Grant the Site guest user access to only the single Apex class.
- **No visibility when it breaks:** We log every call and surface failures on a dashboard with alerts, so an issue never goes unnoticed.
- **Reporting drifts from reality:** External-id keys and a delta timestamp keep Salesforce and the source reconciled, so reports stay trustworthy.

### Authorize.Net-specific gotchas

- The paved tokenize-to-authorize flow supports only single-use tokens; saved cards / CIM profiles must be handled outside it
- Stay PCI-compliant: log to PaymentGatewayLog, never persist the PAN
- Gateway response mapping must translate Authorize.Net result, AVS and CVV codes into CommercePayments GatewayResponse subclasses

## FAQ

**How do you authenticate Authorize.Net with Salesforce?**

We connect Authorize.Net using secure named credentials and store every secret in Salesforce Named Credentials with a permission set, so nothing is hard-coded or shipped in metadata.

**Does the Authorize.Net integration handle bulk volume?**

Yes. All Apex is bulkified, volume moves to Queueable or Batch Apex, and we respect the Salesforce governor limits (SOQL, DML, and callout caps per transaction).

**How do you prevent duplicate records?**

We upsert on a unique external-id field, so a retried or duplicate payload is idempotent and never creates a second Payment.

**How is the integration tested and deployed?**

Apex tests with HttpCalloutMock cover the success, failure, and a 200-record bulk case (75 percent plus coverage). We deploy via change sets or an SFDX and CI pipeline.

**What happens if Authorize.Net or Salesforce is briefly down?**

Failed calls retry with backoff and land in an Error Log object with alerting, so nothing is lost and any event can be replayed.

**How do you secure the webhook endpoint?**

The Apex REST endpoint runs on a Salesforce Site with a locked-down guest user, verifies the HMAC signature on every event, and checks the timestamp to block replays.

---

Maintained by [Cloudsheer](https://www.cloudsheer.com). Full illustrated guide: [Authorize.Net technical guide](https://www.cloudsheer.com/integrations/authorize-net/technical-guide). Want it built for you at a fixed price? [Book a free 30-minute call](https://cal.com/cloudsheer-consulting/30min?overlayCalendar=true).
