Send Pay Links
API Reference

Upsell API

Post-purchase upsell flow management with targeting rules

Upsell API

The Upsell API enables post-purchase 1-click upsells. After a successful checkout, create an upsell session with targeted offers that customers can accept without re-entering payment details.

Flow Overview

1. Customer completes checkout

        v
2. Create upsell session with offers

        v
3. Customer sees first offer

    ┌───┴───┐
    │       │
 Accept   Decline
    │       │
    v       v
4. Charge   Next offer
   saved    (or exit)
   payment

        v
5. Repeat until no more offers

Create Upsell Session

Create an upsell session with targeted offers.

POST /api/upsell/create-session
Content-Type: application/json

Request Body

interface CreateSessionRequest {
  orderId: string;              // New order ID for upsells
  originalOrderId: string;      // Original checkout order ID
  customerEmail: string;
  customerId?: string;
  paymentMethodToken?: string;  // Token from checkout completion
  provider: PaymentProviderType;
  currency?: string;            // Default: "USD"
  offers: UpsellOffer[];
  metadata?: Record<string, string>;

  // For targeting rules
  cartTotal?: number;           // Original cart total in cents
  purchasedProductIds?: string[];
  
  // Theming
  themeId?: string;             // Theme to apply to upsell pages
}

interface UpsellOffer {
  id: string;
  name: string;
  description?: string;
  price: number;                // In cents
  originalPrice?: number;       // For showing discount
  image?: string;
  showIf?: TargetingRules;
}

interface TargetingRules {
  minCartValue?: number;        // Minimum cart value in cents
  productIds?: string[];        // Show only if purchased these
  excludeProductIds?: string[]; // Hide if purchased these
}

Example Request

{
  "orderId": "upsell_order_123",
  "originalOrderId": "order_abc123",
  "customerEmail": "customer@example.com",
  "paymentMethodToken": "pm_1ABC|cus_XYZ",
  "provider": "stripe",
  "currency": "USD",
  "offers": [
    {
      "id": "offer_1",
      "name": "Premium Upgrade",
      "description": "Upgrade to premium for 50% off",
      "price": 4999,
      "originalPrice": 9999,
      "image": "https://example.com/upgrade.jpg",
      "showIf": {
        "minCartValue": 2500
      }
    },
    {
      "id": "offer_2",
      "name": "Extended Warranty",
      "description": "2-year extended warranty",
      "price": 1999,
      "showIf": {
        "productIds": ["prod_123"]
      }
    }
  ],
  "cartTotal": 9999,
  "purchasedProductIds": ["prod_123"],
  "themeId": "theme_xyz123"
}

Success Response

{
  "success": true,
  "sessionId": "sess_abc123",
  "upsellUrl": "/upsell/sess_abc123",
  "expiresAt": 1704110400000,
  "totalOffers": 2
}

No Eligible Offers Response

When targeting rules filter out all offers:

{
  "success": true,
  "skipUpsell": true,
  "message": "No eligible upsell offers for this customer"
}

Accept Upsell Offer

Accept an upsell offer and charge the saved payment method.

POST /api/upsell/accept
Content-Type: application/json

Request Body

{
  "sessionId": "sess_abc123",
  "offerId": "offer_1"
}

Response

{
  "success": true,
  "transactionId": "pi_upsell_123",
  "offerAccepted": "offer_1",
  "amountCharged": 4999,
  "hasMoreOffers": true,
  "totalUpsellAmount": 4999
}

Decline Upsell Offer

Decline the current offer and move to the next one.

POST /api/upsell/decline
Content-Type: application/json

Request Body

{
  "sessionId": "sess_abc123",
  "offerId": "offer_1"
}

Response

{
  "success": true,
  "hasMoreOffers": true,
  "nextOfferId": "offer_2"
}

Get Session Status

Retrieve current upsell session status.

GET /api/upsell/session/:id

Response

{
  "success": true,
  "session": {
    "id": "sess_abc123",
    "status": "active",
    "currentStep": 1,
    "totalOffers": 2,
    "acceptedOffers": ["offer_1"],
    "declinedOffers": [],
    "totalUpsellAmount": 4999,
    "expiresAt": 1704110400000
  }
}

Session Statuses

StatusDescription
activeSession is active, offers available
completedAll offers shown, session complete
expiredSession expired (30 minute limit)

For headless or external upsell flows (e.g., triggering an upsell accept from a blog post or custom landing page), use Action Links. These links handle the payment logic securely and redirect the user to the next step.

Format:

https://[YOUR_DOMAIN]/upsell/action/[ACTION]/[SESSION_ID]

Supported Actions

ActionURL PatternDescription
Accept/upsell/action/accept/{sessionId}Accepts the current offer, processes payment, and redirects to next step.
Decline/upsell/action/decline/{sessionId}Declines the current offer and redirects to next step or downsell.

Redirect Behavior

  1. Success: If there is a "Next Node" in your Flow, the user is redirected there.
    • If the next node has an externalPageUrl property, the user is sent to that URL + ?sessionId=....
  2. Completion: If the flow is finished, the user is redirected to the /success page (or your configured successUrl).

Example Integration

<!-- On your external landing page -->
<a href="https://checkout.mysite.com/upsell/action/accept/sess_abc123" class="btn btn-primary">
  Yes! Add to Order ($19.99)
</a>

Targeting Rules

Offers can include targeting rules to show/hide based on the original purchase:

Minimum Cart Value

Show offer only if the original cart total meets a minimum:

{
  "showIf": {
    "minCartValue": 5000
  }
}

Product-Based Targeting

Show offer only if customer purchased specific products:

{
  "showIf": {
    "productIds": ["prod_123", "prod_456"]
  }
}

Exclusion Rules

Hide offer if customer purchased certain products (e.g., don't offer warranty on a product they already bought warranty for):

{
  "showIf": {
    "excludeProductIds": ["warranty_123"]
  }
}

Combined Rules

Rules can be combined. All conditions must be met:

{
  "showIf": {
    "minCartValue": 2500,
    "productIds": ["prod_electronics"],
    "excludeProductIds": ["bundle_complete"]
  }
}

Payment Method Tokens

The paymentMethodToken enables 1-click charging without re-entering payment details.

Token Formats by Provider

ProviderFormatNotes
Stripepm_xxx|cus_xxxPaymentMethod ID and Customer ID
NMIvault_xxx|txn_xxxCustomer vault and original transaction
Sticky.iosticky_orderIdOriginal Sticky order for add-to-order
KonnektiveorderIdOriginal Konnektive order

Retrieving Token

The token is returned from /api/checkout/complete:

{
  "success": true,
  "orderId": "order_123",
  "paymentMethodToken": "pm_1ABC|cus_XYZ"
}

Or retrieve it later:

POST /api/checkout/get-payment-token
Content-Type: application/json

{
  "provider": "stripe",
  "transactionId": "pi_123"
}

Best Practices

Offer Design

  1. Limit offers: 2-3 offers maximum per session
  2. Show value: Display original price and discount percentage
  3. Clear descriptions: Explain what the customer is getting
  4. Relevant images: Product images increase conversion

Session Management

  1. Handle expiration: Sessions expire after 30 minutes
  2. Track analytics: Log acceptance/decline rates
  3. Test targeting: Verify targeting rules before production

Error Handling

Common error scenarios:

// Session expired
{
  "error": "Session expired",
  "status": 400
}

// Invalid offer ID
{
  "error": "Offer not found in session",
  "status": 404
}

// Payment failed
{
  "error": "Payment failed: Card declined",
  "status": 402
}

Code Example

Complete upsell flow implementation:

// After successful checkout
const checkoutResult = await fetch('/api/checkout/complete', {
  method: 'POST',
  body: JSON.stringify({
    orderId: 'order_123',
    transactionId: 'pi_abc',
    provider: 'stripe',
    // ... other fields
  }),
});

const { paymentMethodToken, orderId } = await checkoutResult.json();

// Create upsell session
const sessionResult = await fetch('/api/upsell/create-session', {
  method: 'POST',
  body: JSON.stringify({
    orderId: `upsell_${orderId}`,
    originalOrderId: orderId,
    customerEmail: 'customer@example.com',
    paymentMethodToken,
    provider: 'stripe',
    offers: [
      {
        id: 'premium_upgrade',
        name: 'Premium Upgrade',
        price: 4999,
        originalPrice: 9999,
      },
    ],
    cartTotal: 9999,
    purchasedProductIds: ['prod_123'],
  }),
});

const session = await sessionResult.json();

if (session.skipUpsell) {
  // Redirect to success page
  window.location.href = '/success';
} else {
  // Redirect to upsell page
  window.location.href = session.upsellUrl;
}

On this page