Skip to content
Drop Loop
Developers · REST API v1

One API for the whole drawing lifecycle.

Everything on this page is what the hosted storefront and admin portal are built on. If they can do it, so can you — from your own code, in any language that speaks HTTPS.

/api/v1
every route, versioned
/api/swagger
OpenAPI 3 document + UI
X-Api-Key
one header to integrate
quick startlist open drops
curl https://api.yourhost.example/api/v1/Lottery/list \
  -H "X-Api-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{}'
200application/json
{
  "total": 3,
  "items": [
    {
      "id": "velocity-4-midnight",
      "status": "Open",
      "closesOn": "2026-10-05T14:00:00Z",
      "winnerCount": 25,
      "backupCount": 10
    }
  ]
}

Composable filters in the body, paging in the query — the same shape on every resource.

Authentication

People sign in; integrations hold a key. Both are checked against live permissions on every request, so a revoked grant stops working immediately — nothing is baked into a token.

A key is issued by a signed-in user and can hold only permissions that user holds in full — delegation, never escalation. Holding one action in an area never mints a key for the whole area.

Our TypeScript client is generated from the OpenAPI document at /api/swagger — the same client the hosted apps run on — so nothing they can do is missing from it.

At a glance

Sessions
JWT bearer, short-lived, refresh rotation
Integrations
API key in the X-Api-Key header
Scope
A subset of the issuing user’s permissions
Storage
Shown once at issue, kept only as a hash
Revocation
Immediate, with a per-key usage history

Resources

Every resource follows the same shape — get, list with composable filters, count, create, update, delete, plus bulk variants — so the second one you learn is free.

Items

What is being dropped: taxonomy, price, images, variants and stock.

Lotteries

The drop itself: window, winner and backup counts, pools, entry rules, allocations.

Entries and wins

One entry per user per drop with a quantity; wins carry the claim window and rank.

Orders

Created on claim with the winner’s fulfilment choice; fulfilled or cancelled by you.

Notifications and subscriptions

The in-app inbox, per-channel preferences and “notify me” follows on any entity.

Webhooks

Endpoints, subscribed events, secret rotation, test pings and delivery history.

Deletes are soft. Every write is audited with who, what, when and from where, and each entity keeps a human-readable event timeline.

The drop lifecycle

Six states, one direction. A scheduler moves drops along on time; every transition is a conditional write, so a draw can never run twice and a crashed draw resumes with the same winners.

  1. Draft

    Being configured. Never visible publicly.

  2. Open

    Accepting entries until the published close.

  3. Closed

    Entries locked, draw seed committed as a hash.

  4. Drawing

    The draw runs — a gate no second draw can pass.

  5. Drawn

    Winners claiming, standbys promoted on a pass.

  6. Completed

    Every unit claimed or returned to stock.

Cancelled, only before a draw

Once a draw has run there are winners with claim windows; after that point a drop can be completed, never undone.

Inventory-gated opening

A drop opens only if stock covers it after what other live drops on the item already commit.

Stock moves on claim

A claim creates an order and decrements stock; cancelling restores it and releases the win to the next standby.

Public endpoints for your storefront

A storefront has visitors before it has users, so browsing needs no token. These routes are anonymous and rate-limited, keyed by your host.

Entering, claiming and preferences are authenticated, with ownership enforced server-side. Draft and cancelled drops are never visible here, and the draw seed never leaves the API before the draw has run.

Anonymous · GET

GETLottery/public-list/{hostId}
Every launched drop on a host — the storefront’s home page.
GETLottery/public/{hostId}/{id}
One drop with its item, price, window and sibling colourways.
GETLottery/public-results/{hostId}/{id}
Masked results once drawn, plus the revealed seed and entrant hash.
GETLottery/public-verify/{hostId}/{id}
Replays the draw from the audit snapshot on the revealed seed.
GETBranding/{hostId}
Colours, logo and tenant defaults, for a sign-in screen with no token yet.
Outbound · signed · retried

Webhooks

Register an https endpoint and subscribe to flat event names — any resource’s create, update or delete, the curated lifecycle events, or wildcards such as lottery.*. A test ping is one call away.

  • lottery.opened
  • lottery.closed
  • lottery.drawn
  • lottery.completed
  • lottery.cancelled
  • lottery.win.claimed
  • lottery.win.declined
  • lottery.win.expired
  • lottery.win.promoted
  • order.fulfilled
  • order.cancelled

At-least-once, de-duplicate on the id

Exponential backoff; retries and redeliveries are byte-identical to the original payload.

Secrets derived, never stored

Reveal on demand; rotation is a version bump with a hard cutover.

Self-healing endpoints

Keep failing and the endpoint is disabled automatically; re-enable with one call.

Exact tenant matching

An endpoint receives its own tenant’s events and never another’s.

headers on every attempt
DropLoop-Signature: t=1757095214,v1=<hex>
DropLoop-Webhook-Id: <deliveryId>
DropLoop-Webhook-Event: lottery.drawn
DropLoop-Webhook-Attempt: 1
verifyHMAC-SHA256 over "{t}.{rawBody}"
const [t, v1] = signature
  .split(',')
  .map((part) => part.slice(part.indexOf('=') + 1))

const expected = hmacSha256Hex(secret, `${t}.${rawBody}`)

if (!timingSafeEqual(expected, v1)) reject()
if (Math.abs(nowSeconds() - Number(t)) > 300) reject()

Compute over the raw request body, compare in constant time, and reject stale timestamps. The secret is the whole whsec_… string.

Verifying a draw

Commit, draw, reveal. Anyone can check that the seed was fixed before the entrant list was known and that the published winners follow from it — without trusting you or us.

  1. 01

    Commit at close

    When the window closes, a random seed is generated and only its SHA-256 hash is stored with the drop. The seed itself never leaves the API before the draw.

  2. 02

    Draw from the seed

    Entrants are sorted and hashed, then winners and standbys are picked from a random source seeded with that seed. The snapshot and every pick are audited.

  3. 03

    Reveal and replay

    Results reveal the seed, the entrant hash and the algorithm. The public verify endpoint replays the draw and reports whether the published winners follow.

Real-time, limits and safety

The parts that keep an integration honest under load.

Real-time notifications

The in-app inbox is pushed live over a SignalR hub at /hubs/notifications, authenticated with the same bearer token. Email, SMS and push fan out per notification type according to your host’s channel map, and every user controls their own mutes per type and channel.

Limits and status codes

Rate limits
Anonymous, auth and export routes, per client address
402 plan_limit_exceeded
A hard cap tripped; the response names the meter
412 precondition failed
A stale client never silently overwrites newer state
409 conflict
Exactly-once inserts: a duplicate claim or number is refused

Ship your next drop on the API.

Get a host provisioned, an API key issued and a webhook pointed at your stack — then run a real draw end to end.