Developers
Webhooks
PRAMAAN sends signed HTTP POSTs to your endpoint when domain events occur. Signature format, retry behavior, and event access are confirmed during production onboarding.
Quick start
- 1. Email [email protected] with your https endpoint URL + the event types you want.
- 2. Receive your sandbox secret through the approved onboarding channel; store it in your secrets vault.
- 3. Verify every incoming POST's
X-PRAMAAN-Signatureheader using HMAC-SHA256 over`${timestamp}.${rawBody}`with your secret. - 4. Respond with
2xx. Retry timing is confirmed with your environment during onboarding.
Signature verification
A PRAMAAN webhook signature header may look like t=1731450012,v1=abc123… where HMAC delivery is enabled. Parse the timestamp t and signature v1. Compute the expected signature. Reject deliveries where the timestamp is > 5 minutes old (prevents replay).
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyPramaan(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')));
const t = parseInt(parts.t, 10);
if (Math.abs(Date.now() / 1000 - t) > 300) return false; // 5-min window
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return timingSafeEqual(Buffer.from(parts.v1, 'hex'), Buffer.from(expected, 'hex'));
}Python
import hmac, hashlib, time
def verify_pramaan(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in header.split(','))
t = int(parts['t'])
if abs(time.time() - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.{raw_body.decode()}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(parts['v1'], expected)Events
badge.issuedv1A worker has completed signup and received their PRAMAAN badge.
{
"worker_id": "wkr_…",
"badge_slug": "rajesh-bengaluru-9821",
"verdict": "green",
"score": 91
}badge.reissuedv1A worker re-signed their badge (lost phone, phone change). Old slug is revoked.
{
"worker_id": "wkr_…",
"old_slug": "rajesh-bengaluru-9821",
"new_slug": "rajesh-bengaluru-2034",
"reissue_count": 1
}badge.revokedv1A badge was revoked by an authorized action, status review, or worker erasure path.
{
"worker_id": "wkr_…",
"badge_slug": "rajesh-bengaluru-9821",
"reason": "erasure"
}verification.verdict_emittedv1A resident verification payment was captured and a verdict was computed.
{
"verification_id": "ver_…",
"worker_id": "wkr_…",
"verdict": "green",
"trust_score": 91
}payment.capturedv1A PRAMAAN payment was captured by the processor for the applicable verification or badge plan.
{
"payment_id": "pay_…",
"amount_total_paise": 5782,
"kind": "resident_verification"
}payment.adjustedv1A payment adjustment was recorded where supported by the applicable policy.
{
"payment_id": "pay_…",
"adjustment_amount_paise": 9900,
"reason": "policy_review"
}worker.dsr_erasedv1A worker erasure request completed where retention and legal holds allowed deletion.
{
"erased_subject_kind": "worker",
"tombstone": "erased_a4b8c2d…",
"request_id": "req_…"
}resident.dsr_erasedv1A resident DSR erasure cascade completed.
{
"erased_subject_kind": "resident",
"tombstone": "erased_…",
"request_id": "req_…"
}Delivery guarantees
- • At-least-once delivery and retry posture are confirmed during production onboarding.
- • Idempotent event_id — same event delivered twice has the same id (use it as your dedup key).
- • Strict-order across one subscription is NOT guaranteed — events from different worker IDs can interleave.
- • Failed deliveries should be handled by your dead-letter process; PRAMAAN will document enabled retry states for your account.
- • Subscription disablement policies are communicated before production traffic is enabled.