Marketing Offer Webhooks Guide

Marketing Offer Webhooks Guide

Marketing offer webhooks deliver pre-calculated funding offers for your merchants directly to your systems. This guide covers the webhook payload structure, processing requirements, and integration patterns for both Advance-level and Account-level offers.

Overview

Liberis generates marketing offers daily for eligible merchants. These offers include Business Cash Advances (BCA), Pay with Liberis, and Flex Capital upgrades. The webhook delivers a complete snapshot of all current offers for each merchant.

Who Receives Webhooks

Marketing offer webhooks are sent to partners who have registered a webhook endpoint and have merchants with revenue data in the Liberis system.

Delivery Cadence

Webhooks are delivered daily for each eligible merchant. The daily delivery serves several purposes:

  • Input data changes daily - Revenue data, payment history, and other merchant metrics are updated continuously
  • Offers are validated daily - Each day, offers are re-evaluated against current data to ensure accuracy
  • Time affects calculations - The passage of time changes offer parameters like estimated repayment timelines

Reliability signal

The daily cadence helps distinguish between "no webhook received due to delivery failure" and "offer unchanged". If you haven't received a webhook within your expected daily window, investigate potential delivery issues rather than assuming offers are unchanged.

Understanding Marketing Offers

Marketing offers are categorised by their offer level, which determines what type of product or action they represent.

Offer Levels

LevelDescription
ADVANCEIndividual funding products the merchant can apply for
ACCOUNTAccount-level changes that affect the merchant's overall relationship with Liberis

Advance Offer Types

TypeDescription
BCA_INITIALFirst-time Business Cash Advance for new customers
BCA_RENEWALRenewal advance for merchants who have previously completed an advance
PAY_WITH_LIBERISPartner-funded advance product with different fee structures

Account Offer Types

TypeDescription
FLEX_UPGRADEUpgrade to a Flex Capital facility, providing a pre-authorised limit for instant advances
FLEX_RENEWALRenewal of an existing Flex Capital facility with updated terms

Approval Statuses

Each offer includes an approvalStatus indicating how much verification is required:

StatusDescription
SPECULATIVEPreliminary offer based on limited data; full application required
ELIGIBLEMerchant meets eligibility criteria; standard application process applies
PRE_APPROVEDMerchant has been pre-approved; streamlined process with minimal additional checks
NO_DECISION_NEEDEDOffer can be actioned immediately without further approval

Webhook Payload Structure

Envelope Structure

Every webhook follows a standard envelope format:

{
  "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "eventDate": "2025-02-01T12:01:02-05:00",
  "type": "liberis.capitalAccounts.v3.marketingOfferCreated",
  "data": { ... }
}
FieldTypeDescription
eventIdUUIDUnique identifier for this webhook event
eventDateISO 8601Timestamp when the event was generated
typeStringAlways liberis.capitalAccounts.v3.marketingOfferCreated
dataObjectThe marketing offer payload

Data Object Fields

FieldTypeDescription
partnerClientIdsArrayYour identifiers for this merchant (may include multiple if merchant has multiple locations)
customerHasCapitalAccountBooleanWhether the merchant already has a Liberis capital account provisioned
doNotMarketBooleanIf true, suppress all marketing to this merchant
campaignSegmentStringOptional segment identifier for targeted marketing campaigns
marketingOffersArrayList of available offers

Marketing Offers Array

Each item in the marketingOffers array contains:

FieldTypeDescription
weightedPriorityIntegerDisplay priority (lower numbers = higher priority)
offerIdStringUnique identifier for this specific offer
offerLevelStringEither ADVANCE or ACCOUNT
expiryISO 8601When this offer expires (UTC)
offerObjectOffer details (structure varies by offer level)

Advance Offer Schema

Advance-level offers (offerLevel: "ADVANCE") include detailed pricing:

{
  "weightedPriority": 1,
  "offerId": "adv123456",
  "offerLevel": "ADVANCE",
  "expiry": "2025-06-01T00:00:00Z",
  "offer": {
    "type": "BCA_INITIAL",
    "amount": {
      "currency": "USD",
      "amount": 10000.00
    },
    "merchantFee": {
      "currency": "USD",
      "amount": 1000.00
    },
    "partnerFee": {
      "currency": "USD",
      "amount": 0.00
    },
    "merchantTotal": {
      "currency": "USD",
      "amount": 11000.00
    },
    "partnerTotal": {
      "currency": "USD",
      "amount": 0.00
    },
    "estimatedTimeToPay": 120,
    "splitPercentage": 15,
    "balanceBeforeAdvance": {
      "currency": "USD",
      "amount": 2000.00
    },
    "balanceAfterAdvance": {
      "currency": "USD",
      "amount": 13000.00
    },
    "limits": {
      "min": {
        "currency": "USD",
        "amount": 5000.00
      },
      "max": {
        "currency": "USD",
        "amount": 15000.00
      }
    },
    "approvalStatus": "ELIGIBLE"
  }
}
FieldDescription
amountRepresentative funding amount for this offer
merchantFeeFee charged to the merchant
partnerFeeFee charged to or shared with the partner
merchantTotalTotal amount the merchant will repay
partnerTotalTotal partner contribution (for Pay with Liberis)
estimatedTimeToPayExpected duration in days to repay the advance
splitPercentagePercentage of daily revenue applied to repayment
balanceBeforeAdvanceMerchant's existing balance before this advance
balanceAfterAdvanceMerchant's total balance if they take this advance
limits.min / limits.maxMinimum and maximum funding amounts available

Account Offer Schema

Account-level offers (offerLevel: "ACCOUNT") have a simpler structure:

{
  "weightedPriority": 3,
  "offerId": "acc789101",
  "offerLevel": "ACCOUNT",
  "expiry": "2025-07-15T00:00:00Z",
  "offer": {
    "type": "FLEX_UPGRADE",
    "limits": {
      "min": {
        "currency": "USD",
        "amount": 1000.00
      },
      "max": {
        "currency": "USD",
        "amount": 5000.00
      }
    },
    "approvalStatus": "PRE_APPROVED"
  }
}
FieldDescription
typeEither FLEX_UPGRADE or FLEX_RENEWAL
limits.min / limits.maxRange of pre-authorised limit amounts available
approvalStatusLevel of pre-approval for this account upgrade

Processing Webhooks

State Snapshot Semantics

Critical

Each webhook represents a complete snapshot of current offers, not an incremental update. This design ensures your system always has the authoritative state.

When you receive a webhook:

  1. The marketingOffers array contains all current offers for this merchant
  2. Any offer type not present in the array should be removed from your system
  3. Any offer type present should replace any existing offers of that type

Reconciliation Logic

Follow this algorithm when processing each webhook:

1. Receive webhook for merchant (identified by partnerClientIds)
2. Parse all offer types from the marketingOffers array
3. For each offer type present in the webhook:
   - Replace any existing offers of that type with the new data
4. For each offer type previously stored but NOT in the webhook:
   - Remove those offers from your system
5. Respond with 204 No Content

Example scenario:

Previous state for merchant ABC123:

  • BCA_INITIAL offer (offerId: "adv001")
  • FLEX_UPGRADE offer (offerId: "acc001")

New webhook contains:

  • BCA_RENEWAL offer (offerId: "adv002")
  • FLEX_UPGRADE offer (offerId: "acc002")

Result:

  • Remove BCA_INITIAL (no longer present)
  • Add BCA_RENEWAL (new type)
  • Replace FLEX_UPGRADE (updated offer)

Idempotent Processing

Your webhook handler must be idempotent. You may receive the same webhook multiple times due to:

  • Network retries
  • System recovery processes
  • Redelivery after transient failures

Use the eventId to detect duplicate deliveries if needed, but the state snapshot design means reprocessing the same webhook should produce the same result.

Response Requirements

You must respond with 204 No Content to acknowledge successful receipt.

HTTP/1.1 204 No Content

Any other response (including 2xx codes other than 204) may trigger retry behaviour. Non-2xx responses will definitely trigger retries with exponential backoff.

Handling Offer Validity

Expiry Dates

Every offer includes an expiry field with a UTC timestamp. You must check this before displaying offers:

if (offer.expiry < currentTime) {
  // Do not display this offer
  // Use GET endpoint to fetch fresh offers
}

Expired offers may still appear in webhooks during the processing window. Always validate expiry at display time.

doNotMarket Flag

When doNotMarket is true, you must suppress all marketing communications to this merchant. This typically indicates:

  • The merchant's account is frozen
  • There are compliance concerns
  • The merchant has opted out of marketing

When this flag is set:

  • The marketingOffers array will be empty
  • Do not display any cached offers
  • Do not send marketing communications

Continuous Validation

Implement validation at multiple points:

  1. On webhook receipt - Store offers with their expiry dates
  2. On page render - Check expiry before displaying
  3. Background job (optional) - Periodically clean up expired offers

Integration Patterns

Webhook + GET Endpoint Pattern

For optimal reliability, combine webhook processing with on-demand retrieval:

sequenceDiagram
    participant L as Liberis
    participant Y as Your System
    participant M as Merchant UI

    L->>Y: Daily webhook with offers
    Y->>Y: Store offers locally
    M->>Y: Merchant views dashboard
    Y->>Y: Check offer expiry
    alt Offers valid
        Y->>M: Display cached offers
    else Offers expired or missing
        Y->>L: GET /v3/marketing_offers/{partnerClientId}
        L->>Y: Current offers
        Y->>Y: Update local cache
        Y->>M: Display fresh offers
    end

Use the GET endpoint as a fallback when:

  • Webhooks may have been missed
  • Offers have expired
  • Real-time accuracy is critical
GET /v3/marketing_offers/{partnerClientId}

Display Priority

Use the weightedPriority field to order offers in your UI. Lower numbers indicate higher priority based on partnership goals.

offers.sort((a, b) => a.weightedPriority - b.weightedPriority)

Present the highest-priority offers most prominently while still giving merchants visibility into all available options.

Complete Payload Examples

Example: Advance and Account Offers

A merchant with both advance products and a Flex Capital upgrade available:

{
  "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "eventDate": "2025-02-01T12:01:02-05:00",
  "type": "liberis.capitalAccounts.v3.marketingOfferCreated",
  "data": {
    "partnerClientIds": ["ABC123", "DEF456"],
    "customerHasCapitalAccount": true,
    "doNotMarket": false,
    "campaignSegment": "high_value",
    "marketingOffers": [
      {
        "weightedPriority": 1,
        "offerId": "adv123456",
        "offerLevel": "ADVANCE",
        "expiry": "2025-06-01T00:00:00Z",
        "offer": {
          "type": "PAY_WITH_LIBERIS",
          "amount": {
            "currency": "USD",
            "amount": 10000.00
          },
          "merchantFee": {
            "currency": "USD",
            "amount": 0.00
          },
          "partnerFee": {
            "currency": "USD",
            "amount": 0.00
          },
          "merchantTotal": {
            "currency": "USD",
            "amount": 10000.00
          },
          "partnerTotal": {
            "currency": "USD",
            "amount": 100.00
          },
          "estimatedTimeToPay": 120,
          "splitPercentage": 15,
          "balanceBeforeAdvance": {
            "currency": "USD",
            "amount": 2000.00
          },
          "balanceAfterAdvance": {
            "currency": "USD",
            "amount": 12000.00
          },
          "limits": {
            "min": {
              "currency": "USD",
              "amount": 5000.00
            },
            "max": {
              "currency": "USD",
              "amount": 15000.00
            }
          },
          "approvalStatus": "ELIGIBLE"
        }
      },
      {
        "weightedPriority": 2,
        "offerId": "adv123457",
        "offerLevel": "ADVANCE",
        "expiry": "2025-06-01T00:00:00Z",
        "offer": {
          "type": "BCA_INITIAL",
          "amount": {
            "currency": "USD",
            "amount": 10000.00
          },
          "merchantFee": {
            "currency": "USD",
            "amount": 1000.00
          },
          "partnerFee": {
            "currency": "USD",
            "amount": 0.00
          },
          "merchantTotal": {
            "currency": "USD",
            "amount": 11000.00
          },
          "partnerTotal": {
            "currency": "USD",
            "amount": 0.00
          },
          "estimatedTimeToPay": 120,
          "splitPercentage": 15,
          "balanceBeforeAdvance": {
            "currency": "USD",
            "amount": 2000.00
          },
          "balanceAfterAdvance": {
            "currency": "USD",
            "amount": 13000.00
          },
          "limits": {
            "min": {
              "currency": "USD",
              "amount": 5000.00
            },
            "max": {
              "currency": "USD",
              "amount": 15000.00
            }
          },
          "approvalStatus": "ELIGIBLE"
        }
      },
      {
        "weightedPriority": 3,
        "offerId": "acc789101",
        "offerLevel": "ACCOUNT",
        "expiry": "2025-07-15T00:00:00Z",
        "offer": {
          "type": "FLEX_UPGRADE",
          "limits": {
            "min": {
              "currency": "USD",
              "amount": 1000.00
            },
            "max": {
              "currency": "USD",
              "amount": 5000.00
            }
          },
          "approvalStatus": "PRE_APPROVED"
        }
      }
    ]
  }
}

Example: Advance Offers Only

A merchant eligible for advance products but not account-level changes:

{
  "eventId": "4fb96f74-6828-4673-c4gd-3d074g77bgb7",
  "eventDate": "2025-02-01T12:01:02-05:00",
  "type": "liberis.capitalAccounts.v3.marketingOfferCreated",
  "data": {
    "partnerClientIds": ["MER789"],
    "customerHasCapitalAccount": true,
    "doNotMarket": false,
    "campaignSegment": "renewal",
    "marketingOffers": [
      {
        "weightedPriority": 1,
        "offerId": "adv789012",
        "offerLevel": "ADVANCE",
        "expiry": "2025-05-15T00:00:00Z",
        "offer": {
          "type": "BCA_RENEWAL",
          "amount": {
            "currency": "USD",
            "amount": 15000.00
          },
          "merchantFee": {
            "currency": "USD",
            "amount": 1350.00
          },
          "partnerFee": {
            "currency": "USD",
            "amount": 0.00
          },
          "merchantTotal": {
            "currency": "USD",
            "amount": 16350.00
          },
          "partnerTotal": {
            "currency": "USD",
            "amount": 0.00
          },
          "estimatedTimeToPay": 90,
          "splitPercentage": 20,
          "balanceBeforeAdvance": {
            "currency": "USD",
            "amount": 500.00
          },
          "balanceAfterAdvance": {
            "currency": "USD",
            "amount": 16850.00
          },
          "limits": {
            "min": {
              "currency": "USD",
              "amount": 10000.00
            },
            "max": {
              "currency": "USD",
              "amount": 25000.00
            }
          },
          "approvalStatus": "PRE_APPROVED"
        }
      }
    ]
  }
}

Example: doNotMarket Scenario

A merchant whose account is frozen:

{
  "eventId": "5gc07g85-7939-5784-d5he-4e185h88chc8",
  "eventDate": "2025-02-01T12:01:02-05:00",
  "type": "liberis.capitalAccounts.v3.marketingOfferCreated",
  "data": {
    "partnerClientIds": ["FRZ001"],
    "customerHasCapitalAccount": true,
    "doNotMarket": true,
    "campaignSegment": null,
    "marketingOffers": []
  }
}

When you receive this payload:

  1. Remove all stored offers for this merchant
  2. Suppress any marketing communications
  3. Do not display funding options in your UI

Related Resources