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_codemints discount codes;orbit_manualrecords 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: falseuntil you connect the provider’s API credentials in the dashboard. Before that, an issuance targeting one is recorded with statusmanualand reasonprovider_not_configuredrather than calling the external API.
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:- Route. Resolve the provider (your pin, or the reward type’s default) and check it supports the reward type.
- Fulfil. A
discount_coderesolves immediately to statusissuedwith the minted code in the response. Acustomreward, or any gift card on an unconnected provider, resolves to statusmanualwith a machine-readablereason(awaiting_tenant_fulfillmentorprovider_not_configured), so a downstream webhook or operations queue picks it up. Statusespending,delivered, andfailedmodel the asynchronous lifecycle a connected gift-card network drives through provider callbacks.
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.
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
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.Related reading
- Wallet Passes — loyalty cards and pass distribution.
- Billing overview — wallet balances and balance credits.
- Campaigns end to end — outbound campaign setup that can trigger referral and survey rewards.