Set up Express Checkout

Follow these steps to set up Affirm Express Checkout, including checkout configuration, shipping options, and total calculations.

❗️

Interested in offering Affirm Express Checkout?

Reach out to your Affirm Account Manager to confirm availability and for next steps on building your APIs.

Prerequisites

  • Create an account in the Affirm Merchant Dashboard.
  • Integrate the default Affirm Checkout.
  • Have access to your public and private API keys.
  • Reach out to your Affirm Account Manager to confirm availability and for next steps on building your APIs.

Steps

1. Create a Unique Order Identifier

Create a unique order_id (e.g., cart or session ID) in your backend to track the checkout session. This ID is used throughout the Express Checkout flow and is returned in subsequent steps to link shipping calculations and order totals to the correct transaction. Use a high-entropy identifier (such as a UUID) to improve security, rather than a sequential value.

2. Configure Affirm Express Checkout

You must configure Affirm's checkout object specifically for the Express Checkout flow:

  • Set the checkout type: Include checkout_variant: "express" in the merchant object.
  • Include an order ID: Pass the order_id generated in Step 1 to track the checkout session. This ID is returned in later steps.
  • Provide the subtotal: Include a subtotal value representing the cost of items before taxes and shipping.
  • Exclude shipping and total: Do not include the shipping object or total field, as these are determined during the Affirm-hosted flow.
  • Include standard attributes: Provide the items array and all other required checkout fields.

affirm.checkout({
  merchant: {
    checkout_variant: "express", // ensure this attribute is passed for Express Checkout
    user_confirmation_url: "https://merchantsite.com/confirm",
    user_cancel_url: "https://merchantsite.com/cancel",
    public_api_key: "YOUR_PUBLIC_KEY",
    user_confirmation_url_action: "POST",
  },
  order_id: "unique_merchant_cart_identifier", // merchant's unique order identifier
  metadata: {
    subtotal: 20000, // cost of items excluding taxes or shipping
    // rest of metadata attributes
  },
  items: [{
    display_name: "Awesome Pants",
    sku: "ABC-123",
    unit_price: 10000,
    qty: 2,
  }],
  // exclude the shipping object and the total field
  // rest of checkout object attributes
});

affirm.checkout.open();

3. Set-up Shipping & Totals HTTP endpoint

Provide a server-side endpoint that Affirm calls during checkout to retrieve shipping options and calculate the order totals associated with each option.

  • Accepts the cart items and customer shipping details, and,
  • Returns the calculated shipping amount, tax amount, and total order amount for the selected shipping method.

Request and response flow

  • Affirm collects the customer’s shipping address during checkout.
  • Affirm sends a server-to-server request to your endpoint with:
    • order_id,
    • shipping address.
  • Your endpoint returns:
    • Available shipping options.
    • Shipping cost, tax, and total for each option.

The customer selects a shipping option in the Affirm experience, and the corresponding total is used to underwrite the loan. This finalized amount is later returned to your backend for validation during transaction authorization.

Configuration

The Shipping & Totals endpoint URL is configured internally by Affirm and is not directly accessible or editable by merchants.

To update this URL or provide separate environments (e.g., sandbox and production), contact your Technical Account Manager (TAM).

Endpoint security

To ensure requests originate from Affirm, you must verify the request signature included in the X-Affirm-Signature header.

Signature format

Affirm generates an HMAC-SHA512 hash of the current timestamp and the request body using your Private API Key. The header is formatted as:

"t={current_timestamp},v0={hash("{current_timestamp}.{request_body}")}"

Verify the request

On your server:

  1. Extract the timestamp and v0 signature from the header.
  2. Recompute the HMAC-SHA512 hash using your Private API Key.
  3. Compare your computed hash to the v0 value.
  4. Reject the request if:
    1. The signatures do not match → return HTTP 401 Unauthorized,
    2. The timestamp is older than 5 minutes.
Key rotation

During key rotation, Affirm may include a hash value generated with multiple keys. The header value is:

"t={current_timestamp},v0={key1_hash}={key2_hash}"

Please note that key1 is the older of the two keys.

Secure communications

All communication between Affirm and your server-side endpoints must be conducted over HTTPS using TLS 1.2 or higher.

Endpoint performance

  • Response time: Your Shipping & Totals endpoint must respond within 5 seconds (5000 ms).
  • Timeout behavior: If the endpoint does not respond within this window, the request will time out, resulting in a checkout error and preventing the customer from completing their purchase.

Handling Calculation & Validation Errors

If your backend is unable to calculate shipping options or order totals due to restricted shipping zones, invalid address data, or inventory shortages, you must return an HTTP 422 Unprocessable Entity status code.

When this error occurs, Affirm expects:

  • error_code: Affirm parses this machine-readable string to determine the internal logic and flow handling.
  • message: This string is displayed directly to the customer within the Affirm checkout interface to explain why the checkout cannot proceed.
  • fields: (Optional) An array of strings identifying the specific request payload properties that caused the error (e.g., ["shipping_address.country"]).

For detailed schema requirements and example payloads, please refer to the Express Checkout API page.

Example

On user shipping address selection, Affirm will send a request with a payload similar to the following:

{
    "currency":"USD",
    // The merchant order id provided by the merchant
    "order_id": "unique_merchant_cart_identifier",
    // https://docs.affirm.com/developers/reference/shipping-billing-object
    "shipping":{
        "line1": "123 Example Street",
        "line2": "Apt 123",
        "city": "San Francisco",
        "country": "US",
        "state": "CA",
        "zipcode": "94107"
    }
}

If the request is successful, Affirm will expect a HTTP 200 response similar to the following:

{
  "order_id": "unique_merchant_cart_identifier",
  "currency": "USD",
  "subtotal": 20000,
  "shipping_options": [
    {
      "shipping_type": "unique_merchant_shipping_identifier_1",
      "shipping_label": "Standard Shipping (7-10 Days)",
      "shipping_amount": 0,  
      "tax_amount": 100,
      "total": 20100  
    },
    {
      "shipping_type": "unique_merchant_shipping_identifier_2",
      "shipping_label": "Express Shipping (3-5 Days)",
      "shipping_amount": 200,  
      "tax_amount": 100,
      "total": 20300  
    },
    {
      "shipping_type": "unique_merchant_shipping_identifier_3",
      "shipping_label": "Overnight Shipping (1 Day)",
      "shipping_amount": 500,  
      "tax_amount": 100,
      "total": 20600  
    }
  ]
}

If the request is unsuccessful, the endpoint should return a 422 Unprocessable Entity status code. The response body must contain an errors array with one or more error objects:

Single error
{
  "errors": [
    {
      "error_code": "UNSUPPORTED_SHIPPING_ZONE",
      "message": "We currently do not offer shipping to Hawaii or Alaska.",
      "fields": [
        "shipping_address.state"
      ]
    }
  ]
}
Multiple errors
{
  "errors": [
    {
      "error_code": "INVALID_SHIPPING_ADDRESS",
      "message": "The provided ZIP code does not match the selected state.",
      "fields": [
        "shipping_address.zipcode",
        "shipping_address.state"
      ]
    },
    {
      "error_code": "INVENTORY_UNAVAILABLE",
      "message": "One or more items in your cart are no longer in stock."
    }
  ]
}

4. Calculate Order Total

After the customer’s Affirm checkout is complete, retrieve the finalized order details from Affirm and verify the total before completing the transaction.

Retrieve checkout details

Use the checkout_id to call the Read Checkout API:

GET /api/v2/checkout/{checkout_id}

This returns the full shipping details including:

  • Shipping address
  • Selected shipping method
  • Final shipping, tax, and total amounts

Use this data to calculate the final order total in your backend.

🚧

Order ID

If your system generates a new order ID at this stage that differs from the initial ID provided in Step 1, ensure you pass this updated identifier when authorizing the transaction.

From this point on, use the updated order ID as the primary reference for all subsequent Transaction API calls.

Validate the total

Call the Authorize Transaction endpoint to retrieve the authorized loan amount from Affirm.

Compare the following:

  • Your calculated total.
  • Affirm’s authorized amount.

These values must match exactly to ensure the legally authorized loan covers the final order cost before proceeding.

Finalize the order

If the totals match:

  • Complete the transaction.
  • Route the customer to your receipt page.

If they do not match:

  • Do not fulfill the order.
  • Treat as a validation failure.
Sample Read Checkout API response
{
  "order_id": "unique_merchant_cart_identifier",
  "currency": "USD",
  // order totals collected during user checkout journey
  "total": 20600,
  "tax_amount": 100,
  "shipping_amount": 500,
  "shipping": {
    // shipping method collected during user checkout journey  
    "shipping_type": "unique_merchant_shipping_identifier_3",
    "shipping_label": "Standard Shipping (7-10 Days)"
    "name": {
      "full": "John Doe",
      "first": "John",
      "last": "Doe"
    },
    "address": {
      "city": "San Francisco",
      "country": "US",
      "line1": "123 Example Street",
      "line2": "Apt 123",
      "state": "CA",
      "zipcode": "94107"
    },
    "email": "[email protected]",
    "phone_number": "1234567890"
  },
  "merchant": {
    "checkout_variant": "express", // flag for whether this checkout was Express
    "public_api_key": "string",
    "user_cancel_url": "string",
    "user_confirmation_url": "string",
    "user_confirmation_url_action": "string",
    "name": "string",
  },
  // rest of checkout data
  "metadata": {
    "subtotal": 20000 // subtotal
  },
  "billing_frequency": "monthly",
  "financial_program_external_name": "string",
  "financial_program_name": "standard_3_6_12",
  "loan_type": "classic",
  "financing_program": "string",
  "merchant_external_reference": "ab-12345",
  "billing": {
    "name": {
      "last": "string",
      "first": "string"
    },
    "phone_number": "1234567890",
    "email": "[email protected]"
  },
  "mfp_rule_input_data": {
    "items": {
      "sku_number": {
        "sku": 0,
        "item_url": "string",
        "display_name": "string",
        "unit_price": 0,
        "qty": 0,
        "item_type": "string",
        "item_image_url": "string"
      }
    },
    "total": 49999,
    "metadata": {
      "checkout_channel_type": "online",
      "mode": "redirect"
    },
    "financing_program": "string"
  },
  "checkout_type": "merchant",
  "checkout_flow_type": "classic",
  "checkout_status": "string",
  "use_adaptive": true,
  "config": "string",
  "product_type": "string",
  "api_version": "v2",
  "product": "string",
  "suppress_expiration_declination_messaging": true,
  "meta": {
    "release": "string",
    "user_timezone": "America/Los_Angeles",
    "_affirm_tracking_uuid": "356a483a-86b2-4846-b6f2-70d37d95a78c"
  }
}

(Optional) 5. Add Affirm Checkout Button

Add the pre-styled Affirm Checkout button on your website for Express Checkout. See Checkout Button for details.

Example:

<div class="affirm-checkout-button-container"
     data-page-type="product"
     data-size="large"
     data-theme="dark"
     data-shape="rounded"
     data-button-text="checkout"
>
</div>

What’s next?