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 takingwebhookSigningSecretand a 4-arg form takingNotifizzClientOptions.client.track(eventName, properties)— emits one event; throwsIOExceptionon 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 (Instantor ISO 8601String).- Each
track()retries twice (1s, then 2s) before bubblingIOException. client.enricher(name, options),client.declareEvent(name, options)andclient.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: onApplicationReadyEvent), so a non-production environment re-runs discovery immediately instead of on the next periodic pass.NotifizzClientimplementsCloseable—close()releases the pooled HTTP connections.
Installation
- Maven
- Gradle (Kotlin DSL)
- Gradle (Groovy)
Constructor
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 }toPOST /v1/events/track— plusoccurredAtwhen you declare one. - Sends
Authorization: Bearer <sdkSecretKey>andX-Idempotency-Key: <idempotencyKey>. - Retries transient failures twice (1s, then 2s) — three total attempts.
- Throws
java.io.IOExceptionif 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.
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 sameMap.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.
Returns —
String, 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:
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.
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.
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). InstantiateNotifizzClient 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:
IOException is rethrown — the SDK does not silently swallow failures.
The full error catalogue (including HTTP status mappings) is in error catalogue.
FAQ
Why does `track()` throw `IOException`?
Why does `track()` throw `IOException`?
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.Should I generate the idempotency key myself?
Should I generate the idempotency key myself?
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.Can I host an enricher from my Java service?
Can I host an enricher from my Java service?
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.Is `track()` blocking?
Is `track()` blocking?
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.How do I configure a custom base URL for staging?
How do I configure a custom base URL for staging?
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.Can I batch several events in one call?
Can I batch several events in one call?
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.