Skip to main content
See /concepts/lifecycle for the end-to-end flow (policy → trigger → bond → wait/sell decision).
Polling is wasteful. Subscribe a URL, the API will POST JSON when something happens.

Subscribe

const sub = await lumina.webhooks.create({
  url:    'https://my-bot.example.com/webhooks/lumina',
  events: ['policy_purchased', 'policy_triggered', 'bond_minted'],
})
console.log('STORE THIS NOW:', sub.secret)
The secret is a 32-byte hex string. Returned exactly once. Store it in a secret manager — there’s no API to retrieve it later.

Available events

EventFires when
policy_purchasedA purchasePolicyFor call completes successfully
policy_triggeredA trigger is accepted on-chain and a bond is minted
bond_mintedSame trigger event, surfaced from the BondVault perspective
bond_redeemedA bond is redeemed for $LUMINA at maturity (730 days post-mint)
listing_createdYour wallet listed a bond on the marketplace
listing_purchasedYour listing was bought by another wallet
Use events: '*' to subscribe to everything for the wallet (recommended for new integrations).

Payload schema

Every delivery is JSON with the same envelope:
{
  "id":        "evt_01HXY…",        // unique event ID (use for de-dup)
  "event":     "policy_triggered",
  "createdAt": 1715974321,           // unix seconds
  "wallet":    "0xYourWalletAddress",
  "data":      { /* event-specific payload, see below */ }
}
Event-specific data shapes:
// policy_purchased
{ "policyId": "0x…", "productId": "0x…", "productName": "FLASHBTC24-001",
  "coverageAmount": "1000000000", "premiumPaid": "12345678", "txHash": "0x…" }

// policy_triggered  /  bond_minted
{ "policyId": "0x…", "bondId": "202805", "faceValueUsdc": "800000000",
  "triggeredAt": 1715974321, "txHash": "0x…" }

// bond_redeemed
{ "bondId": "202805", "amountRedeemed": "800000000", "luminaPaid": "…", "txHash": "0x…" }

// listing_created  /  listing_purchased
{ "listingId": "…", "bondId": "202805", "amount": "50",
  "totalPriceUsdc": "49000000", "txHash": "0x…" }

Headers on every delivery

HeaderValue
Content-Typeapplication/json
X-Lumina-Signaturehex(HMAC-SHA256(rawBody, secret))
X-Lumina-EventThe event name, e.g. policy_purchased
X-Lumina-DeliveryA unique delivery ID for de-duplication

Verifying the signature (Express example)

import crypto from 'crypto'
import express from 'express'

const app = express()

// IMPORTANT: use raw body, not the JSON-parsed one — the HMAC is over the
// exact bytes the API sent.
app.post('/webhooks/lumina', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.header('X-Lumina-Signature') ?? ''
  const expected = crypto.createHmac('sha256', process.env.LUMINA_WEBHOOK_SECRET!)
    .update(req.body)
    .digest('hex')

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).send('bad signature')
  }

  const payload = JSON.parse(req.body.toString('utf8'))
  console.log('event:', req.header('X-Lumina-Event'), payload)
  res.sendStatus(200)
})
Always compare with crypto.timingSafeEqual (or your language’s equivalent constant-time comparator) to prevent timing attacks. Always verify against the raw body, never the JSON-decoded representation.

Retry semantics

  • Acceptance: any 2xx response counts as delivered.
  • 4xx: marked failed permanently (the receiver said no).
  • 5xx / network: retried with exponential backoff (30s · 2^attempt).
  • Max attempts: 3 (so the longest gap from event to final failure is ~150s).
The worker polls the queue every 30 seconds. Latency from event to first delivery attempt is therefore 0–30s. Use X-Lumina-Delivery as a de-duplication key — the same event can be re-delivered if your endpoint times out then later succeeds.

List and revoke

const subs = await lumina.webhooks.list()    // metadata only — no secret
await lumina.webhooks.delete(subs[0].id)     // owner-only

Limits

  • One URL per wallet. Re-registering the same (wallet, url) pair returns 409. Delete and re-create to rotate the secret.
  • Multiple URLs allowed. Different URLs (e.g. dev/staging/prod) can coexist on the same wallet.
  • HTTPS required in production. http://localhost is allowed for testing.