OfferKitDocs

Customers and orders

Map your customer IDs and order records into OfferKit, choose the source of truth for each field, and understand what decision requests actually read.

Customer and order context powers assignment, per-user limits, targeting, loyalty, referrals, checkout decisions, and support history. The safest integration uses your existing IDs at the boundary and clearly chooses which system owns each field.

Map customers by external ID

Use the stable, immutable user ID from your application as externalId:

const result = await offerkit.customers.upsert({
  externalId: user.id,
  email: user.email,
  name: user.name,
  phone: user.phone,
  metadata: {
    plan: user.plan,
    region: user.region,
  },
});

const customer = result.customer;

Upsert is idempotent on externalId. Existing customers are updated in place, omitted fields are left unchanged, and result.created tells you whether a new row was made.

Use customers.getByExternalId for lookup when you do not have the OfferKit UUID:

const customer = await offerkit.customers.getByExternalId({
  params: { externalId: user.id },
});

Do not use an email address as the primary mapping if customers can change email or share one. Do not create a new OfferKit customer on every checkout attempt.

Choose a sync point

Common patterns are:

  • Account lifecycle: upsert on registration and relevant profile changes.
  • Just in time: upsert before the first promotion, loyalty, or referral operation.
  • Event driven: send changes through an application outbox or worker.

Just-in-time upsert is the simplest reliable default. Add event-driven updates when targeting depends on data that must remain fresh outside checkout.

Design metadata deliberately

Metadata is part of the targeting contract. Prefer stable, documented fields with predictable types:

{
  "plan": "pro",
  "region": "AE",
  "lifecycle": "active",
  "marketingOptIn": true
}

Avoid copying sensitive data that rules do not need. Plan how fields are renamed or removed, because an old value can continue to affect eligibility.

Send cart context without creating an order

Voucher validation, redemption, and promotion qualification accept an inline order context containing an amount, currency, and optional product items. This is enough for most checkout decisions and does not create an OfferKit order row.

Use inline context when your own commerce system is the order source of truth and you only need OfferKit to decide and record incentives.

Create OfferKit order records when useful

Create an order when operators should search orders in the OfferKit dashboard, inspect their lifecycle, or see attached redemptions:

const offerkitOrder = await offerkit.orders.create({
  externalId: order.id,
  customerId: customer.id,
  items: order.items.map((item) => ({
    productId: item.productId,
    sku: item.sku,
    name: item.name,
    quantity: item.quantity,
    unitPrice: item.unitPriceCents,
  })),
  amount: order.subtotalCents,
  discountAmount: order.discountCents,
  currency: order.currency,
  status: "CREATED",
});

Pass offerkitOrder.id as orderId when redeeming, along with the inline order context used for the decision. You may also pass your own externalOrderId for reconciliation. Passing only that external reference does not attach the redemption to the stored order.

OfferKit order states are CREATED, PAID, FULFILLED, and CANCELED. They record progress from your authoritative order workflow. Creating an order or changing its status does not redeem a voucher, award points, convert a referral, refund a payment, or reverse a benefit. Call those operations separately and store their outcomes.

Understand the two item shapes

Stored orders contain operator-facing item fields such as SKU and name. Runtime discount context uses productId, optional collectionId, quantity, and unit price. Build each shape deliberately rather than serializing a database row blindly.

The order amount sent to OfferKit should match the subtotal basis your promotion policy uses. Decide whether tax and shipping are included, document it, and use the same basis during validation and redemption.

Deletion and retention

Customer and order deletion is soft deletion. Before deleting a customer, decide how their vouchers, loyalty membership, referrals, and audit needs should be handled. Avoid using deletion as a routine sync mechanism for temporarily disabled accounts.

Which ID goes where?

Field or callIdentifier
customers.upsert.externalIdYour application’s immutable user ID
Voucher validate, redeem, qualify, stackEither customerExternalId or the OfferKit UUID in customerId; if both are sent they must identify the same customer
Promotion qualificationOfferKit customerId; it does not accept customerExternalId
Loyalty enrollmentOfferKit customerId and loyalty programId
Loyalty earn, spend, adjust, historyLoyalty memberId, created by enrollment
Referral issueReferral programId and OfferKit referrerCustomerId
Referral convertAdvocate code string and OfferKit refereeCustomerId
orders.create.externalIdYour application’s order ID
Redemption externalOrderIdYour application’s order reference; does not look up or create a stored order
Redemption orderIdUUID of an order already stored in OfferKit
Redemption orderInline amount, currency, and items used for the decision; not an order ID

Voucher validation does not create a customer for an unknown external ID. Voucher qualification and redemption can create a minimal customer with that external ID, without profile metadata. This is why an explicit upsert is useful even when you do not need to save the OfferKit UUID. Use a persisted customer for assignment and customer-data rules.

An unassigned voucher can be used without customer context when no per-user limit or customer-dependent rule requires it. Loyalty and referrals require identified customers. Do not use a session’s changing guest ID as a durable per-person limit.

Your catalog stays in your shop

productId is your stable product or SKU identifier. A collection is a group such as summer-sale; collectionId is the identifier you send for that group. OfferKit has no catalog-sync requirement and does not resolve these IDs against your shop. Match the strings in your rules to the strings in decision items. The runtime item shape accepts one collection ID per item.