Skip to main content

Incentives

Incentives is the rewards primitive behind every “we owe this person something” moment in Orbit: a referral hits its reward step, a survey respondent earns a thank-you, a loyalty redemption pays out, or your support team grants a one-off credit. Direct API access (POST /incentives/issue, plus list, redeem, and void) works like a standalone promotions API you can drive for any trigger: win-back promos, goodwill credits, campaign prizes. The same engine powers the Incentives screen in the dashboard’s Outbound hub, where operators inspect the issued-incentive ledger, redeem a reward, or void one.

Incentive types

Every issuance names a reward type (reward_type). Four types exist: Providers are the fulfillment targets a reward can route to. GET /incentives/providers returns the catalog, and the dashboard reward-source picker renders it. Providers fall into two kinds:
  • Internal (always available): orbit_code mints discount codes; orbit_manual records a custom payout for you to fulfill.
  • External (gift-card networks such as Tremendous or Tango Card): these appear in the catalog as descriptors with configured: false until you connect the provider’s API credentials in the dashboard. Before that, an issuance targeting one is recorded with status manual and reason provider_not_configured rather than calling the external API.
Pin a provider explicitly with provider_id, or omit it and Orbit picks the default for the reward type, preferring a configured provider. A provider can only fulfil the reward types it lists: you cannot mint a discount code through a gift-card network.

Ledger accounting

Issuance, redemption, and voiding are events on an append-only ledger, not rows you mutate. Each incentive issues exactly one record; the events that follow (incentive.issued, then optionally incentive.redeemed or incentive.voided) fold into the incentive’s current state. The lifecycle is short: issued is the only open state; redeemed and voided are terminal. Nothing can move out of a terminal state. This ledger lives inside your tenant and answers the question “what did we hand out, to whom, and where did it end up” for audits and support reviews. GET /incentives/issued reads it, with filters for status, source, reward_type, and contact_id. The ledger tracks what you issued; it is separate from your wallet budget. An incentive is a record of a promised reward, not a pre-funded pool. Balance credits flow through the tenant billing ledger on their own rails, so an incentive cannot silently charge your account twice: credit rewards are refused by the incentive engine and handled by billing instead.

Fulfillment flow and redemption semantics

Issuing an incentive runs the shared fulfillment engine:
  1. Route. Resolve the provider (your pin, or the reward type’s default) and check it supports the reward type.
  2. Fulfil. A discount_code resolves immediately to status issued with the minted code in the response. A custom reward, or any gift card on an unconnected provider, resolves to status manual with a machine-readable reason (awaiting_tenant_fulfillment or provider_not_configured), so a downstream webhook or operations queue picks it up. Statuses pending, delivered, and failed model the asynchronous lifecycle a connected gift-card network drives through provider callbacks.
Retry and idempotency. Send an Idempotency-Key header (or an idempotency_key body field) on POST /incentives/issue. A retry carrying the same key replays the original incentive with its current lifecycle state instead of minting a second reward, and the response marks replayed: true. Concurrent submissions of the same key serialise, so one incentive issues even under a bursty retry storm. Only issue accepts an idempotency key: redeem and void are naturally idempotent because a terminal-state incentive returns a conflict. Redemption. POST /incentives/:id/redeem consumes an issued incentive (with an optional external reference, such as the order the code was applied to) and POST /incentives/:id/void cancels it with a reason. Both transitions are guarded by the state machine: redeeming an already-redeemed incentive, or voiding a redeemed one, returns 409 with the current lifecycle status. Concurrent redeem and void calls on the same incentive serialise; exactly one of them wins.

Where incentives enter the flow

Three product surfaces call the same fulfillment engine server-side, so a reward earned anywhere resolves through one tracked path:
  • Referrals — when a referral rewards a discount code, gift card, or custom perk instead of balance credit, the referral flow issues it through the engine.
  • Surveys — respondent thank-you rewards fulfil through the engine when the survey completes.
  • Loyalty redemption — spending loyalty points on a reward routes through the engine.
Direct API issuance (POST /incentives/issue) is the fourth source, tagged api. Every incentive records which source created it, and you can filter the ledger by source to audit one surface at a time.

Issue an incentive

Issue a gift card through a connected provider by pinning provider_id (for example "tremendous") and sending amount in minor units (cents) with an ISO-4217 currency. The minted code appears on the response as data.code; redeem it later with POST /incentives/{incentive_id}/redeem. Wallet balance credits are a separate primitive: issue them through the wallet API, not POST /incentives/issue. See Billing overview.

Relationship to Wallet Passes

Wallet Passes are the carrier for loyalty cards: the pass in Apple Wallet or Google Wallet holds the customer’s points balance and tier. When a customer redeems points against a loyalty card, that redemption triggers incentive issuance through the same engine described here, and the resulting reward (a code, a gift card, a custom perk) is what the customer actually receives. Wallet Passes carry the program; incentives pay out what the program owes.