Skip to content
PramaanDPDP Live

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. 1. Email [email protected] with your https endpoint URL + the event types you want.
  2. 2. Receive your sandbox secret through the approved onboarding channel; store it in your secrets vault.
  3. 3. Verify every incoming POST's X-PRAMAAN-Signature header using HMAC-SHA256 over `${timestamp}.${rawBody}` with your secret.
  4. 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.issuedv1

A worker has completed signup and received their PRAMAAN badge.

{
  "worker_id": "wkr_…",
  "badge_slug": "rajesh-bengaluru-9821",
  "verdict": "green",
  "score": 91
}
badge.reissuedv1

A 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.revokedv1

A 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_emittedv1

A resident verification payment was captured and a verdict was computed.

{
  "verification_id": "ver_…",
  "worker_id": "wkr_…",
  "verdict": "green",
  "trust_score": 91
}
payment.capturedv1

A 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.adjustedv1

A payment adjustment was recorded where supported by the applicable policy.

{
  "payment_id": "pay_…",
  "adjustment_amount_paise": 9900,
  "reason": "policy_review"
}
worker.dsr_erasedv1

A worker erasure request completed where retention and legal holds allowed deletion.

{
  "erased_subject_kind": "worker",
  "tombstone": "erased_a4b8c2d…",
  "request_id": "req_…"
}
resident.dsr_erasedv1

A 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.

Related developer pages