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.
| Pattern | Use it for | Mechanism |
|---|---|---|
| Synchronous request/response | A user is waiting on screen for one answer or one chart | POST /api/v1/astrology/query, /v2/astrology/* |
| Server-Sent Events (SSE) | Token-by-token interactive output for a chat-style UI | POST /api/v1/astrology/query/stream |
| Webhooks | Multi-page report jobs, batch runs, billing and subscription state changes | You 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
- Job lifecycle —
report.completed,report.failed,batch.completedfor asynchronous report and batch work. - Billing and subscription — renewals, plan upgrades and downgrades, invoice generation. Upgrades apply immediately; downgrades defer to the next cycle, so your mirror should reflect both states.
- Wallet — top-ups and low-balance signals, since the wallet is denominated in cents and per-query usage draws it down.
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
- Kick off the long-running job and store the returned job id alongside the user it belongs to.
- Register a webhook endpoint (an HTTPS URL you control) and its signing secret in your dashboard.
- Receive
report.completedorreport.failedwhen the job resolves; verify the signature, dedupe on event id. - Fetch and store the artifact from the URL in the payload, then notify your user through your own channel.
- Handle failure explicitly — surface
report.failedto 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
- The interactive query endpoint
POST /api/v1/astrology/queryis synchronous; webhooks are for asynchronous report jobs, batch work, and billing or subscription events. - For token-by-token interactive output, use SSE at
/api/v1/astrology/query/streamrather than polling or webhooks. - Always verify deliveries with an HMAC-SHA256 over the raw body, compared in constant time, with a timestamp check to bound replay.
- Webhook delivery is at-least-once; dedupe on the event
idand make side effects idempotent (upsert, not insert). - Acknowledge with a 2xx as soon as the event is durably queued, then process asynchronously to avoid retry storms.
- The API spans 700+ operations across 25 domains, covering Vedic, Western, and KP systems with output in 30 languages.
- Auth uses
x-api-key: vk_live_*against base URLhttps://api.vedika.io; plans run from Starter ($12/mo) to Enterprise ($240/mo) with per-query usage of $0.01-$0.05.
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.