tutorial

Using webhooks with an astrology API

A developer guide to webhook patterns for an astrology API: async report jobs, billing events, signature verification, retries, idempotency, and SSE streaming.

Webhooks let an astrology API push events to your server when work finishes or state changes out-of-band, rather than making you poll. With the Vedika API, the interactive question-and-answer path at POST /api/v1/astrology/query is synchronous and returns in one round trip, so webhooks are not needed there. They earn their keep for the asynchronous parts of an integration: long-running report jobs, batch processing, and billing or subscription events that originate from payment flows. This guide covers when to reach for webhooks versus streaming, the delivery contract, and the security and reliability patterns that keep a webhook consumer correct in production.

When you need webhooks (and when you don't)

The Vedika API exposes 700+ operations across 25 domains (704 enumerated as of June 2026), spanning Vedic (sidereal), Western (tropical), and KP in one surface, plus Jaimini, Tajaka, Lal Kitab, numerology and more. Most of those are computation endpoints that respond immediately. Reach for the right pattern based on how long the work takes and where the event originates.

PatternUse it forMechanism
Synchronous request/responseA user is waiting on screen for one answer or one chartPOST /api/v1/astrology/query, /v2/astrology/*
Server-Sent Events (SSE)Token-by-token interactive output for a chat-style UIPOST /api/v1/astrology/query/stream
WebhooksMulti-page report jobs, batch runs, billing and subscription state changesYou host an HTTPS endpoint; the API POSTs events to it

The distinction matters because each pattern fails differently. A blocked synchronous call ties up a request thread; a dropped SSE connection ends a stream; a missed webhook silently desynchronizes your local state. Webhooks trade immediacy for durability, which is exactly what you want for events a user is not actively waiting on.

Streaming versus webhooks for long answers

It is tempting to reach for webhooks the moment a response feels slow, but interactive output has a better tool. If a user is on the page and you want partial output to appear as it is produced, use SSE. The connection stays open and the server emits incremental events until completion.

curl -N https://api.vedika.io/api/v1/astrology/query/stream \
  -H "x-api-key: vk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "How does my current dasha period affect my career?",
    "birthDetails": {
      "datetime": "1990-08-15T14:30:00",
      "latitude": 18.5204,
      "longitude": 73.8567,
      "timezone": "Asia/Kolkata"
    },
    "speed": "fast"
  }'

Use webhooks instead when nobody is waiting in real time: a 40-page report that takes long enough that holding an HTTP connection open is fragile, an overnight batch of charts, or a billing renewal that fires on its own schedule. The rule of thumb is simple. If the user is watching, stream. If the work outlives the request, webhook.

Anatomy of a webhook event

A well-behaved webhook delivery is a single HTTPS POST with a JSON body and a small set of headers your handler depends on. Expect a stable event identifier, an event type, a timestamp, and a signature. The shape below is representative of what a consumer should parse and is intentionally generic so your handler stays decoupled from any one event type.

// Example event body delivered to your endpoint
{
  "id": "evt_8f2c1a9b",            // stable, unique per event
  "type": "report.completed",       // dot-namespaced event type
  "created": 1718870400,            // unix seconds
  "data": {
    "reportId": "rep_4471",
    "status": "completed",
    "downloadUrl": "https://api.vedika.io/api/v1/reports/rep_4471"
  }
}

Two headers do the heavy lifting: a signature header carrying an HMAC of the raw body, and a timestamp you use to bound replay. Treat the id as your idempotency key and the type as your routing key. Never branch on fields inside data before the signature has been verified.

Common event families to plan for

Verifying signatures the right way

A webhook endpoint is a public URL, so anyone can POST to it. Signature verification is what makes the payload trustworthy. The correct approach is an HMAC-SHA256 over the raw request body — the exact bytes received, before any JSON parser has touched them — compared in constant time against the signature header.

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SIGNING_SECRET = process.env.VEDIKA_WEBHOOK_SECRET;

// Capture the RAW body; do not let JSON middleware reparse it first
app.post('/webhooks/vedika',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.header('x-vedika-signature') || '';
    const timestamp = req.header('x-vedika-timestamp') || '';

    // 1. Bound replay: reject timestamps older than 5 minutes
    const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
    if (!timestamp || ageSeconds > 300) {
      return res.status(400).send('stale or missing timestamp');
    }

    // 2. Recompute the HMAC over timestamp + raw body
    const signed = `${timestamp}.${req.body.toString('utf8')}`;
    const expected = crypto
      .createHmac('sha256', SIGNING_SECRET)
      .update(signed)
      .digest('hex');

    // 3. Constant-time compare
    const ok =
      signature.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
    if (!ok) return res.status(401).send('bad signature');

    const event = JSON.parse(req.body.toString('utf8'));
    // ... hand off to idempotent processing, then ack
    res.status(200).send('ok');
  });

Three details are easy to get wrong. First, parsing the body before hashing changes the bytes (key ordering, whitespace) and breaks verification — always hash the raw buffer. Second, a plain === string compare leaks timing information; use crypto.timingSafeEqual. Third, without a timestamp check a captured-and-replayed request stays valid forever, so reject anything older than a short window.

Idempotency, retries, and ordering

Webhook delivery is at-least-once. Networks drop acknowledgements, your server restarts mid-request, a load balancer times out — and the sender retries. Design every handler so a duplicate delivery is a no-op.

Deduplicate on event id

import hashlib

def process_event(event, db):
    event_id = event["id"]
    # Unique constraint on event_id makes the insert the dedupe gate
    try:
        db.execute(
            "INSERT INTO processed_events (event_id) VALUES (%s)",
            (event_id,),
        )
    except db.UniqueViolation:
        return  # already handled, ack and move on

    if event["type"] == "report.completed":
        store_report(event["data"], db)   # upsert on reportId, not insert
    elif event["type"] == "subscription.updated":
        mirror_subscription(event["data"], db)

Acknowledge fast, process async

Do the minimum synchronously: verify the signature, durably enqueue the event, return 2xx. Heavy work — rendering, downstream emails, third-party calls — belongs on a queue. Holding the response open while you finish slow work invites delivery timeouts and unnecessary retries.

Don't assume ordering

Events can arrive out of order. A subscription.updated can land before the invoice.created that triggered it. Make handlers commutative where possible, and when order genuinely matters, reconcile against the authoritative resource — for billing, your local mirror is a cache, not the source of truth.

A practical reference flow: async report generation

  1. Kick off the long-running job and store the returned job id alongside the user it belongs to.
  2. Register a webhook endpoint (an HTTPS URL you control) and its signing secret in your dashboard.
  3. Receive report.completed or report.failed when the job resolves; verify the signature, dedupe on event id.
  4. Fetch and store the artifact from the URL in the payload, then notify your user through your own channel.
  5. Handle failure explicitly — surface report.failed to the user rather than leaving a spinner running forever.

This decouples your user-facing latency from the API's processing time. The synchronous endpoints remain available for anything interactive; webhooks carry only the events that genuinely happen later.

Local development and testing

You cannot receive a webhook on localhost directly, so use a tunnel to expose your local handler to the internet during development, and start against the free sandbox where no API key is required. Build a small replay harness: capture a real event body once, then re-POST it to your handler with a freshly computed signature to exercise verification, deduplication, and your processing branches without waiting on real jobs. Test the unhappy paths deliberately — a tampered body should 401, a stale timestamp should 400, and a duplicate id should be a clean no-op.

You can prototype the synchronous and streaming endpoints in the free sandbox, review request and response shapes in the API docs, and compare tiers on the pricing page. For the interactive path that complements webhooks, see our guide on streaming astrology responses with SSE.

Where the underlying data comes from

Whichever delivery pattern you use, the computations are produced by the XALEN Ephemeris, Vedika's own open-source astronomical engine (Apache-2.0; available on crates.io, PyPI, and as a WebAssembly package, with roughly 2,200 tests). It is validated against JPL DE440 and swetest, with zero charts deviating beyond 0.1 degrees across a five-million-chart test — ephemeris precision, meaning the planetary positions are astronomically sound. Interpretive claims in reports are attributable to classical sources used in formal training, such as Brihat Parashara Hora Shastra, Phaladeepika, the KP Readers, and Ptolemy's Tetrabiblos. That separation matters for a webhook consumer too: the asynchronous artifact you store and serve to a user rests on the same computed foundation as a synchronous query.

Key facts

Bringing it all together

Webhooks are the right tool for the parts of an astrology integration that happen on their own schedule, while synchronous queries and SSE cover everything a user actively waits on. Choose by latency and origin, verify every delivery, treat duplicates as inevitable, and acknowledge fast. Get those four habits right and your webhook consumer stays correct even as event volume and event types grow.

Build on the Vedika astrology API

700+ operations, Vedic + Western + KP, 30 languages, an open-source XALEN ephemeris, and a built-in LLM. Free sandbox — no signup.

Try the free sandbox