Implementation of incident clients for various notification systems.
| Client | Module | Java Module |
|---|---|---|
| PagerDuty V2 Events API | incident-pagerduty |
software.sava.incident_pagerduty |
| incident.io Incidents V2 | incident-io |
software.sava.incident_io |
| Generic Webhook / Slack / Telegram | incident-webhook |
software.sava.incident_webhook |
All depend on incident-core (software.sava.incident_core), which provides the
provider-neutral IncidentClient
API described below.
final var client = PagerDutyEventClient.clientBuilder()
.defaultClientName("CLIENT_NAME")
.defaultRoutingKey("INTEGRATION_KEY")
.authToken("AUTH_TOKEN")
.createClient();
final var payload = PagerDutyEventPayload.build()
.summary("ex-summary")
.source("ex-source")
.severity(PagerDutySeverity.critical)
.timestamp(ZonedDateTime.now(UTC))
.component("ex-component")
.group("ex-group")
.eventClass("ex-class")
.customDetails("ex-num-metric", 1)
.customDetails("ex-boolean", true)
.customDetails("ex-string", "val")
.link(PagerDutyLinkRef.build()
.href("https://github.com/sava-software/incident-client")
.text("Sava Incident PagerDuty Event Client")
.create())
.image(PagerDutyImageRef.build()
.src("https://www.pagerduty.com/wp-content/uploads/2016/05/pagerduty-logo-green.png")
.href("https://www.pagerduty.com/")
.alt("pagerduty")
.create())
.create();
final var triggerResponseFuture = client.triggerDefaultRouteEvent(payload);
final var changeEventPayload = PagerDutyChangeEventPayload.build(payload).create();
final var changeEventResponseFuture = client.defaultRouteChangeEvent(changeEventPayload);
final var triggerResponse = triggerResponseFuture.join();
final var ackResponse = client.acknowledgeEvent(triggerResponse.dedupKey()).join();
final var resolveResponse = client.resolveEvent(triggerResponse.dedupKey()).join();
final var changeEventResponse = changeEventResponseFuture.join();idempotencyKey and visibility are required by the API; a request without them is
rejected. Severities, incident types, statuses, roles, and custom fields are all
workspace-specific ids — look them up in your incident.io workspace.
try (final var httpClient = HttpClient.newHttpClient()) {
final var client = IncidentIoClient.clientBuilder()
.bearerToken("BEARER_TOKEN")
.httpClient(httpClient)
.createClient();
final var request = CreateIncidentRequest.requestBuilder()
.idempotencyKey(UUID.randomUUID().toString())
.name("Test Incident")
.summary("Test Java client")
.visibility(CreateIncidentRequest.Visibility.PRIVATE)
.mode(CreateIncidentRequest.Mode.test)
.severityId("SEVERITY_ID")
.incidentTypeId("INCIDENT_TYPE_ID")
.incidentRoleAssignments(List.of(new CreateIncidentRequest.IncidentRoleAssignment(
"INCIDENT_ROLE_ID",
CreateIncidentRequest.UserReference.byEmail("responder@example.com"))))
.customFieldValues(Map.of("CUSTOM_FIELD_ID", "value"))
.build();
final var response = client.createIncident(request).join();
System.out.println(response.reference());
System.out.println(response.permalink());
}customFieldValues is sugar for text custom fields; select, catalog, link, and numeric
fields use customFieldEntries with a matching CustomFieldValue — see the
module README. slackChannelNameOverride,
incidentTimestampValues, and retrospectiveIncidentOptions round out
IncidentsCreatePayloadV2.
incident-webhook POSTs a structured message to a single configured webhook endpoint —
fire-and-forget notification, not incident management. WebhookFormats.GENERIC_JSON
(provider id webhook) sends a canonical JSON document of the alert for receivers that
do their own mapping; WebhookFormats.SLACK_TEXT (provider id slack) sends a plain
{"text":"..."} Slack incoming-webhook message; TelegramTextFormat (provider id
telegram) sends a Bot API sendMessage body to
https://api.telegram.org/bot<TOKEN>/sendMessage with a configured chatId.
final var client = WebhookClient.clientBuilder()
.endpoint("https://hooks.slack.com/services/T000/B000/XXXX")
.createClient();
final IncidentClient incidentClient = client.incidentClient(WebhookFormats.SLACK_TEXT);Service-level code can be written against IncidentClient and switch providers via
configuration. IncidentClients creates a client from configuration alone — provider
modules on the module or class path register themselves via ServiceLoader, and the
provider config value selects one:
incident.provider=pagerduty
incident.routingKey=INTEGRATION_KEYincident.provider=incident.io
incident.bearerToken=BEARER_TOKEN
incident.visibility=private
incident.severityIds.CRITICAL=SEVERITY_IDfinal IncidentClient incidentClient = IncidentClients.createClient(properties, "incident");The JSON equivalent wraps the provider's config object, with provider first:
{"provider": "pagerduty", "config": {"routingKey": "INTEGRATION_KEY"}}Adapters can also be created in code from a native client:
final IncidentClient incidentClient = usePagerDuty
? pagerDutyEventClient.asIncidentClient()
: incidentIoClient.incidentClientBuilder()
.visibility(CreateIncidentRequest.Visibility.PRIVATE)
.severityId(IncidentSeverity.CRITICAL, "SEVERITY_ID")
.createClient();
final var alert = IncidentAlert.build()
.summary("Validator missed its leader slot")
.details("No block produced for slot 350000000.")
.severity(IncidentSeverity.CRITICAL)
.source("validator-01.example.com")
.timestamp(ZonedDateTime.now(UTC))
.customDetail("slot", 350000000L)
.create();
final var response = incidentClient.reportIncident(alert).join();
if (incidentClient.supportsResolve()) {
incidentClient.resolveIncident(response.key()).join();
}The incident.io adapter maps IncidentSeverity values onto workspace severity ids
supplied at build time, and has no programmatic resolve: supportsResolve() returns
false and resolveIncident(String) fails the returned future with an
UnsupportedOperationException. It also needs an incidentTimestampId before it can
send IncidentAlert#timestamp() — incident.io timestamps are workspace ids — and carries
source() and customDetails() as text appended to the incident summary, since
incident.io custom fields are workspace schema objects rather than an arbitrary
key/value map. Provider-specific features — PagerDuty links,
images, and change events; incident.io custom fields and role assignments — remain on the
provider clients.