Skip to main content

Notifizz Java SDK reference

com.notifizz:notifizz-java is the JVM SDK for tracking events. It exposes track() overloads, the enricher subsystem, the event catalog, a hashed-token helper for widget auth, and works the same from Java or Kotlin. The package is published to Maven Central.

TL;DR

  • new NotifizzClient(authSecretKey, sdkSecretKey) — plus a 3-arg form taking webhookSigningSecret and a 4-arg form taking NotifizzClientOptions.
  • client.track(eventName, properties) — emits one event; throws IOException on transient failures after retries.
  • client.track(eventName, properties, idempotencyKey) — overload for retried jobs.
  • client.track(eventName, properties, idempotencyKey, occurredAt) — overload declaring when the business event happened (Instant or ISO 8601 String).
  • Each track() retries twice (1s, then 2s) before bubbling IOException.
  • client.enricher(name, options), client.declareEvent(name, options) and client.dispatch(body) give the JVM the same enricher and event-catalog surface as the Node SDK, since 2.0.0.
  • client.ready() — call it once your server is listening (Spring: on ApplicationReadyEvent), so a non-production environment re-runs discovery immediately instead of on the next periodic pass.
  • NotifizzClient implements Closeableclose() releases the pooled HTTP connections.

Installation

No custom repository or credentials are required — Maven Central is used by default.

Constructor

Two more forms exist. Add the signing secret when this service exposes enrichers, and NotifizzClientOptions when you want a schema mode or a rejection callback:

client.track(eventName, properties)

Emits a single event. Notifizz resolves campaigns by eventName and runs each campaign’s orchestrator server-side to build the recipient list — there is no client-side workflow or recipient targeting.

client.track(eventName, properties, idempotencyKey)

Overload with an explicit idempotency key. Use this when the same logical emit may run twice (retry, dedupe).

Parameters

Behaviour

  • Posts { eventName, properties, sdkSecretKey, idempotencyKey } to POST /v1/events/track — plus occurredAt when you declare one.
  • Sends Authorization: Bearer <sdkSecretKey> and X-Idempotency-Key: <idempotencyKey>.
  • Retries transient failures twice (1s, then 2s) — three total attempts.
  • Throws java.io.IOException if all attempts fail or the response status is >= 400.

Event time (occurredAt)

By default, the moment Notifizz receives an event is the moment it happened — right, when you track at the point the thing occurs. Declare the event time when the two genuinely differ: replaying a queue, flushing a batch collected offline, backfilling history.
Both overloads leave the field out of the request when you pass null — an absent field tells the API that reception time is the only truth. A date in the future is refused with event/invalid-occurred-at; a 60-second tolerance absorbs client clock skew. The value feeds time-scoped resolutions server-side, so it must never claim a state that does not exist yet. Available since 2.2.0.

Kotlin

The SDK works naturally with Kotlin — the same Map.of / mapOf interop applies:

client.generateHashedToken(userId)

Generates the SHA-256 of userId + authSecretKey. Pass it to your frontend so the Notification Center widget can authenticate in backendToken mode.
ReturnsString, hex-encoded SHA-256. See backend tokens for the widget side.

client.config(opts)

Overrides default options. Currently only baseUrl is configurable.

Declaring events

Declaring an event registers it in a catalog the orchestrator AI and the dashboard can read — schema, description and idempotency fields included, instead of properties guessed from observed payloads. Declaring is optional; tracking always works without it. Available since 2.0.0.
declareEvent() returns the canonical (trimmed) event name — store it and pass it to track() so a rename stays a one-line change. client.declaredEvents() returns the discovery view for diagnostics and tests. Schemas are plain JSON Schema (Map<String, Object>). JsonSchema is a fluent builder for the common shapes; hand-built maps work just as well.

Validation modes (SchemaMode)

The schema mode is SDK-local. The Notifizz server never blocks a track over a declared schema — strict mode is a guard rail for dev and CI. Set it per environment without touching code:
When idempotencyFields is declared and you pass no key, track() derives a deterministic one from those properties — a retry of the same logical event dedupes on its own. See Events for the catalog UI and the tradeoffs at a higher level.

Enrichers

An enricher is a server-side function the Notifizz orchestrator calls to fetch live data at notification time. You register one per data source, expose them on a public URL, and the backend calls them via HMAC-signed webhooks. Available on the JVM since 2.0.0. The full protocol is in the enrichers protocol reference.

client.enricher(name, options)

Registers an enricher on this client. Call once per enricher, before your dispatch route serves traffic.
client.enrichers() returns the discovery view of what is registered — handy in tests.

client.enrichProfileData(handler)

Registers the system enricher that feeds the Audience profile panel in the dashboard. It differs from enricher() on three points: it takes the reserved name notifizz:profileResolver (hidden from the enricher catalog), its input is fixed to { id, email } — one user per call, no batch — and its output is free-form.
Whatever fields you return are rendered in the profile panel — there is no schema to declare on the way out.

client.dispatch(body)

The body-only webhook entry point. Your controller is a one-liner:
dispatch() accepts the parsed body (Map) or a raw JSON String. It verifies the HMAC over the inner payload, rejects stale timestamps (anti-replay, ±5 min), and routes discovery versus enricher execution. Errors are encoded inside the body, never thrown across the dispatch boundary — your framework writes 200 unconditionally and Notifizz translates error.code back into a typed domain error. That is what keeps the controller one line. dispatch() throws IllegalStateException if no webhookSigningSecret was passed to the constructor. NotifizzClient.signDispatchPayload(secret, payload) produces the same HMAC, for testing a dispatch route with synthetic signed requests.

Audience identity

identify() links two Subjects to the same Audience — the mechanism that makes an application user and an email address the same person. detach() puts a Subject back into an Audience of its own. Available since 2.0.0.

What the action means

Links are always declared, never inferred: Notifizz merges two identities because your code said so, not because a payload looked similar. In a production environment, an EmailSubject on a known disposable domain is refused (disposable_email_domain); outside production the call succeeds and the response carries a warnings entry instead. Both calls throw IOException on a network failure. An HTTP error status is not turned into an exception: the response body is decoded and returned as-is, so check for an action key before trusting the map. Neither call retries.

client.ready()

Signals that this service’s dispatch endpoint is mounted and serving its current set of enrichers and declared events. Call it once, when your HTTP server is listening — in Spring Boot, from an ApplicationReadyEvent listener.
Why a dedicated call rather than the boot signal the constructor already sends: the constructor runs before your enricher() and declareEvent() registrations and before the server binds its port, so at that moment the discovery endpoint cannot answer truthfully yet. ready() fires at the one instant the catalog is actually servable. The payoff is the local loop. In a non-production environment, the signal asks Notifizz to re-run discovery immediately, so an enricher or event property you just added shows up without a dashboard refresh and without waiting for the periodic pass.
Cross-language: ready() exists in the Node and Java SDKs. The PHP SDK has no equivalent — see PHP: boot signal.

Lifecycle

All calls share one pooled HTTP client with bounded timeouts — connect 2 s, response 5 s, pool checkout 2 s (since 2.1.0). Instantiate NotifizzClient once and keep it: a client per request throws the pool away every time and pays a fresh TLS handshake. NotifizzClient implements Closeable; close() releases the pool. A long-lived singleton can skip it — the JVM reclaims the connections at exit — but a managed bean should not: Spring picks close() up as the destroy method automatically.

Error handling

track() declares throws IOException. Wrap it where you need a custom log line:
After three failed attempts, the last IOException is rethrown — the SDK does not silently swallow failures. The full error catalogue (including HTTP status mappings) is in error catalogue.

FAQ

Tracking is a network call to POST /v1/events/track. After the SDK exhausts its 3 retry attempts, the underlying IOException (or a wrapped one for >= 400 HTTP statuses) is rethrown. Always handle it — silent network failures are a foot-gun.
Yes when the same logical emit may be retried (queued jobs, scheduler, retry middleware). Use a deterministic key derived from your domain — order-shipped:{orderId} is better than UUID.randomUUID() from outside the SDK, which generates a new key per attempt and defeats the dedupe.
Yes, since 2.0.0. Register handlers with client.enricher(name, options), pass a webhookSigningSecret to the constructor, and expose client.dispatch(body) on a public route — see Enrichers above. No Node service in the middle.
Yes — track() is synchronous. Since 2.1.0 the wait is bounded: three attempts, each capped at 2 s to connect and 5 s for the response, plus the 1 s + 2 s backoff between them. A fully degraded backend can therefore stall the calling thread for roughly 24 s. Run it on an ExecutorService or a virtual thread when latency matters — and never inside a database transaction, where the wait would hold a connection hostage.
Call client.config(Map.of("baseUrl", "...")) after construction. The SDK reads the option on every track() call. Use this for local mocks, regional endpoints, or staging environments — production is https://api.notifizz.com/v1.
Not today. Each track() call is one event. Fire them in parallel with an executor when batching matters; idempotency keys ensure retries dedupe at the backend.

See also

Event Tracking reference

HTTP wire format, idempotency contract, error shapes.

Backend quickstart

Send your first event in under five minutes.

Event Tracking overview

Cross-language feature matrix.

Notification Center widget

Display the notifications your events drive.