Skip to content

Not legal advice

dpdpkit is software that helps you implement obligations under India's Digital Personal Data Protection Act, 2023 and the DPDP Rules, 2025. It does not provide legal advice and does not guarantee compliance.

Events and notifications

events

In-process event bus and HMAC-signed outbound webhooks.

Event handlers run after the ledger write they describe. A failing handler is logged and recorded in EventBus.failures; it never rolls back or blocks the state change.

Event

Bases: BaseModel

id instance-attribute

id: str

type instance-attribute

type: str

tenant_id instance-attribute

tenant_id: str

occurred_at instance-attribute

occurred_at: datetime

data class-attribute instance-attribute

data: dict[str, Any] = Field(default_factory=dict)

EventBus

EventBus()

failures instance-attribute

failures: list[tuple[Event, str]] = []

subscribe

subscribe(event_type: str, handler: Handler) -> Handler

Subscribe to one type, or "*" for all. Returns the handler so it works as a decorator.

on

on(event_type: str) -> Callable[[Handler], Handler]

publish

publish(event_type: str, tenant_id: str, data: dict[str, Any], at: datetime | None = None) -> Event

WebhookEndpoint

Bases: BaseModel

id instance-attribute

id: str

url instance-attribute

url: str

secret instance-attribute

secret: str

events class-attribute instance-attribute

events: list[str] = Field(default_factory=lambda: ['*'])

active class-attribute instance-attribute

active: bool = True

wants

wants(event_type: str) -> bool

WebhookDispatcher

WebhookDispatcher(endpoints: Iterable[WebhookEndpoint] = (), *, sender: Sender = urllib_sender, retries: int = 3, backoff_seconds: float = 0.5, sleep: Callable[[float], None] = sleep)

Delivers events to customer-configured endpoints. Failed deliveries land in dead_letters.

Adapters that need durable retries persist dead_letters and call redeliver.

endpoints instance-attribute

endpoints = list(endpoints)

sender instance-attribute

sender = sender

retries instance-attribute

retries = retries

backoff_seconds instance-attribute

backoff_seconds = backoff_seconds

sleep instance-attribute

sleep = sleep

dead_letters instance-attribute

dead_letters: list[tuple[WebhookEndpoint, Event, str]] = []

attach

attach(bus: EventBus) -> None

dispatch

dispatch(event: Event) -> None

deliver

deliver(endpoint: WebhookEndpoint, event: Event) -> bool

redeliver

redeliver() -> int

sign_payload

sign_payload(secret: str | bytes, body: bytes, timestamp: int | None = None) -> str

Return the signature header value: t=<unix>,v1=<hex hmac of "t.body">.

verify_signature

verify_signature(secret: str | bytes, body: bytes, header: str, *, tolerance: int = 300, now: int | None = None) -> bool

Verify a webhook signature header; rejects anything older than tolerance seconds.

notify

Notification interface (email, SMS, WhatsApp) with delivery receipts.

Core never talks to a provider directly. A Notifier turns a Message into a DeliveryReceipt. The default NullNotifier reports every message as failed, so nothing that depends on a delivered warning (such as erasure) can run until a real transport is set.

Notifier

Bases: Protocol

send

send(message: Message) -> DeliveryReceipt

NullNotifier

No transport configured: every send fails, which keeps fail-safe paths closed.

send

send(message: Message) -> DeliveryReceipt

MemoryNotifier

MemoryNotifier(status: DeliveryStatus = DELIVERED)

Records messages in sent. status controls the outcome (for tests).

status instance-attribute

status = status

sent instance-attribute

sent: list[Message] = []

send

send(message: Message) -> DeliveryReceipt

ConsoleNotifier

ConsoleNotifier(write: Callable[[str], object] = print)

Prints messages. For local development only; it reports delivery without real delivery.

write instance-attribute

write = write

send

send(message: Message) -> DeliveryReceipt

SmtpNotifier

SmtpNotifier(host: str, port: int = 587, *, sender: str, username: str | None = None, password: str | None = None, starttls: bool = True, use_ssl: bool = False, timeout: float = 15.0)

Email over SMTP (stdlib). SMS and WhatsApp messages are refused (failed receipt).

host instance-attribute

host = host

port instance-attribute

port = port

sender instance-attribute

sender = sender

username instance-attribute

username = username

password instance-attribute

password = password

starttls instance-attribute

starttls = starttls

use_ssl instance-attribute

use_ssl = use_ssl

timeout instance-attribute

timeout = timeout

send

send(message: Message) -> DeliveryReceipt

render

render(template: str, locale: str, context: Mapping[str, object]) -> tuple[str, str]

Render a template in locale, falling back to English.

ses

Amazon SES email transport (pip install "dpdpkit-core[ses]").

By default a message SES accepts is reported as delivered, like SmtpNotifier. With track_delivery=True it is reported as queued and tagged with the dpdpkit receipt id; the final outcome comes from SES event publishing (configuration set → SNS → SQS) and is applied with poll_ses_events. A pre-erasure warning then only counts once SES reports the mailbox accepted it.

Events are read from SQS with your AWS credentials rather than received on a public URL: a forged "delivered" event would start an erasure clock, so the channel must be authenticated.

SesNotifier

SesNotifier(sender: str, *, region: str | None = None, configuration_set: str | None = None, track_delivery: bool = False, client: Any = None)

Send email with the SES v2 API. SMS and WhatsApp messages are refused (failed receipt).

sender instance-attribute

sender = sender

configuration_set instance-attribute

configuration_set = configuration_set

track_delivery instance-attribute

track_delivery = track_delivery

client property

client: Any

send

send(message: Message) -> DeliveryReceipt

poll_ses_events

poll_ses_events(kit: Kit, queue_url: str, *, region: str | None = None, client: Any = None, max_batches: int = 10, wait_seconds: int = 0) -> int

Drain SES events from an SQS queue (subscribed to the configuration set's SNS topic, raw or not). A message is deleted only after it has been applied. Returns how many outcomes were recorded.

apply_ses_event

apply_ses_event(kit: Kit, event: Mapping[str, Any]) -> str | None

Record the final outcome of a tracked message. Pending pre-erasure warnings are confirmed or moved to needs_attention; other receipts are updated in place. Returns the receipt id, if any.

ses_event_outcome

ses_event_outcome(event: Mapping[str, Any]) -> tuple[str, bool, str | None] | None

Read an SES event (or an SNS envelope around one). Returns (receipt_id, delivered, error), or None when the event is not final (delay, open, complaint) or was not sent by dpdpkit.