Reference layout demo. Full live docs are published at docs.lunedata.io.
Reference

Webhooks

Receive events when transactions are enriched, when carbon thresholds are crossed, when budgets tip, or when subscriptions are detected. At-least-once delivery, signed payloads, retries with exponential backoff.

Register an endpoint

Register your HTTPS endpoint and choose which event types you want.

POST /v1/webhooks

{
  "url": "https://your-backend.example.com/lune-webhook",
  "events": ["transaction.enriched", "budget.exceeded"],
  "description": "Production sync"
}

The response includes a signing_secret. Store it — you'll use it to verify every incoming payload.

Event types

EventFired when
transaction.enrichedA transaction has been enriched (sync or batch).
transaction.refund_matchedA new refund was matched to an existing transaction.
subscription.detectedA recurring charge has been classified as a subscription.
budget.exceededA user's category budget threshold was crossed.
goal.achievedA user reached a savings goal target.
carbon.thresholdA user crossed a monthly carbon-footprint threshold.

Payload shape

{
  "id": "evt_01HXYZABC...",
  "type": "transaction.enriched",
  "created_at": "2026-05-23T14:02:11Z",
  "data": {
    "transaction_id": "txn_...",
    "merchant": { /* same shape as Enrich response */ },
    "category": "food.coffee"
  }
}

Verifying signatures

Each request carries an X-Lune-Signature header — an HMAC-SHA256 of the raw body signed with your endpoint's signing_secret. Always verify before processing.

import crypto from 'node:crypto';

function verify(rawBody, sig, secret) {
  const expected = crypto.createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(sig),
  );
}
import hmac, hashlib

def verify(raw_body: bytes, sig: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, sig)
func verify(rawBody []byte, sig, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(rawBody)
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(sig))
}

Retries

If your endpoint returns anything other than a 2xx status (or times out after 10 seconds), Lune retries with exponential backoff: 30s, 2m, 10m, 1h, 6h, 24h, then gives up and marks the event as failed. Failed events can be replayed from the dashboard or POST /v1/webhooks/events/:id/retry.

Best practices