Build and run Kiket extensions with a batteries-included, strongly-typed Java toolkit.
- π Webhook handlers β register handlers for events with
sdk.register("issue.created", "v1", handler). - π Transparent authentication β HMAC verification for inbound payloads, workspace-token client for outbound calls.
- π Secret manager β list, fetch, rotate, and delete extension secrets stored in Google Secret Manager.
- π Built-in Spring Boot app β serve extension webhooks locally or in production without extra wiring.
- π Version-aware routing β register multiple handlers per event and propagate version headers on outbound calls.
- π¦ Manifest-aware defaults β automatically loads
extension.yaml/manifest.yaml, applies configuration defaults, and hydrates secrets fromKIKET_SECRET_*environment variables. - π§± Typed & documented β designed for Java 17+ with full type safety and rich Javadoc comments.
- π Telemetry & feedback hooks β capture handler duration/success metrics automatically.
- π Custom data client β call
/api/v1/ext/custom_data/...withcontext.getEndpoints().customData(projectId)using the configured extension API key. - π Rate-limit helper β inspect
/api/v1/ext/rate_limitviacontext.getEndpoints().rateLimit()before fanning out jobs.
<dependency>
<groupId>dev.kiket</groupId>
<artifactId>kiket-sdk</artifactId>
<version>0.1.0</version>
</dependency>import dev.kiket.sdk.KiketSDK;
import dev.kiket.sdk.handler.WebhookHandler;
public class Main {
public static void main(String[] args) {
KiketSDK sdk = KiketSDK.builder()
.webhookSecret("sh_123")
.workspaceToken("wk_test")
.extensionId("com.example.marketing")
.extensionVersion("1.0.0")
.build();
// Register webhook handler (v1)
sdk.register("issue.created", "v1", (payload, context) -> {
String summary = (String) ((Map) payload.get("issue")).get("title");
System.out.println("Event version: " + context.getEventVersion());
context.getEndpoints().logEvent("issue.created", Map.of("summary", summary));
context.getSecrets().set("WEBHOOK_TOKEN", "abc123");
return Map.of("ok", true);
});
// Register webhook handler (v2)
sdk.register("issue.created", "v2", (payload, context) -> {
String summary = (String) ((Map) payload.get("issue")).get("title");
context.getEndpoints().logEvent("issue.created", Map.of(
"summary", summary,
"schema", "v2"
));
return Map.of("ok", true, "version", context.getEventVersion());
});
sdk.run("0.0.0.0", 8080);
}
}When your manifest defines custom_data.permissions, configure extensionApiKey(...) (or set KIKET_EXTENSION_API_KEY) so outbound calls include X-Kiket-API-Key:
sdk.register("issue.created", "v1", (payload, context) -> {
String projectId = String.valueOf(((Map<?, ?>) payload.get("issue")).get("project_id"));
var customData = context.getEndpoints().customData(projectId);
var options = new CustomDataClient.CustomDataListOptions();
options.setLimit(10);
options.setFilters(Map.of("status", "active"));
customData.list("com.example.crm.contacts", "automation_records", options);
customData.create("com.example.crm.contacts", "automation_records", Map.of(
"email", "[email protected]",
"metadata", Map.of("source", "webhook")
));
return Map.of("ok", true);
});SLA monitors raise workflow.sla_status events. Use the helper to inspect current alerts:
sdk.register("workflow.sla_status", "v1", (payload, context) -> {
String projectId = String.valueOf(((Map<?, ?>) payload.get("issue")).get("project_id"));
var slaClient = context.getEndpoints().slaEvents(projectId);
var options = new SlaEventsClient.SlaEventsListOptions();
options.setState("imminent");
options.setLimit(5);
var events = slaClient.list(options);
if (events.getData().isEmpty()) {
return Map.of("ok", true);
}
var first = events.getData().get(0);
context.getEndpoints().logEvent("sla.warning", Map.of(
"issue_id", first.get("issue_id"),
"state", first.get("state")
));
return Map.of("acknowledged", true);
});KIKET_WEBHOOK_SECRETβ Webhook HMAC secret for signature verificationKIKET_WORKSPACE_TOKENβ Workspace token for API authenticationKIKET_EXTENSION_API_KEYβ Extension API key for/api/v1/ext/**endpoints (custom data client)KIKET_BASE_URLβ Kiket API base URL (defaults tohttps://kiket.dev)KIKET_SDK_TELEMETRY_URLβ Telemetry reporting endpoint (optional)KIKET_SDK_TELEMETRY_OPTOUTβ Set to1to disable telemetryKIKET_SECRET_*β Secret overrides (e.g.,KIKET_SECRET_API_KEY)
Create an extension.yaml or manifest.yaml file:
id: com.example.marketing
version: 1.0.0
delivery_secret: sh_production_secret
settings:
- key: API_KEY
secret: true
- key: MAX_RETRIES
default: 3
- key: TIMEOUT_MS
default: 5000Main SDK class for building extensions.
KiketSDK sdk = KiketSDK.builder()
.webhookSecret(String)
.workspaceToken(String)
.baseUrl(String)
.settings(Map<String, Object>)
.extensionId(String)
.extensionVersion(String)
.manifestPath(String)
.autoEnvSecrets(boolean)
.telemetryEnabled(boolean)
.feedbackHook(FeedbackHook)
.telemetryUrl(String)
.build();Methods:
sdk.register(String event, String version, WebhookHandler handler)β Register a webhook handlersdk.run(String host, int port)β Start the Spring Boot serversdk.stop()β Stop the server
Context passed to webhook handlers:
public interface HandlerContext {
String getEvent();
String getEventVersion();
Map<String, String> getHeaders();
KiketClient getClient();
ExtensionEndpoints getEndpoints();
Map<String, Object> getSettings();
String getExtensionId();
String getExtensionVersion();
ExtensionSecretManager getSecrets();
}When you are ready to cut a release:
- Update the version in
pom.xml. - Run the test suite (
mvn test). - Build distributables:
mvn clean package
- Commit and tag the release:
git add pom.xml git commit -m "Bump Java SDK to v0.x.y" git tag java-v0.x.y git push --tags - GitHub Actions will automatically publish to GitHub Packages.
MIT
Throttle expensive webhooks by checking the remaining window:
sdk.register("automation.dispatch", "v1", (payload, context) -> {
RateLimitInfo limits = context.getEndpoints().rateLimit();
if (limits != null && limits.getRemaining() < 5) {
context.getEndpoints().logEvent("rate_limited", Map.of(
"remaining", limits.getRemaining(),
"reset_in", limits.getResetIn()
));
return Map.of("deferred", true);
}
// Continue with heavy work
return Map.of("ok", true);
});