.NET
Kiket .NET SDK.
Build and run Kiket extensions with a batteries-included, strongly-typed .NET toolkit.
Features
- ๐ Webhook handlers โ register handlers for events with
sdk.Register("case.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 ASP.NET Core 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. - ๐ Custom data client โ call
/api/v1/ext/custom_data/...withcontext.Endpoints.CustomData(projectId)using the runtime token. - ๐ Rate-limit helper โ call
context.Endpoints.GetRateLimitAsync()to inspect/api/v1/ext/rate_limitbefore launching heavy jobs. - ๐งฑ Typed & documented โ designed for .NET 8.0 with full type safety and rich XML documentation.
- ๐ Telemetry & feedback hooks โ capture handler duration/success metrics automatically.
Quickstart
dotnet add package Kiket.SDKusing Kiket.SDK;
var sdk = new KiketSDK(new SDKConfig
{
WorkspaceToken = "wk_test",
ExtensionId = "com.example.marketing",
ExtensionVersion = "1.0.0"
});
// Register webhook handler (v1)
sdk.Register("case.created", "v1", async (payload, context) =>
{
var summary = payload["case"]["title"].ToString();
Console.WriteLine($"Event version: {context.EventVersion}");
await context.Endpoints.LogEventAsync("case.created", new Dictionary<string, object>
{
["summary"] = summary
});
await context.Secrets.SetAsync("WEBHOOK_TOKEN", "abc123");
return new { ok = true };
});
// Register webhook handler (v2)
sdk.Register("case.created", "v2", async (payload, context) =>
{
var summary = payload["case"]["title"].ToString();
await context.Endpoints.LogEventAsync("case.created", new Dictionary<string, object>
{
["summary"] = summary,
["schema"] = "v2"
});
return new { ok = true, version = context.EventVersion };
});
sdk.Run("0.0.0.0", 8080);Custom Data Client
When your manifest declares custom_data.permissions, the SDK automatically uses the runtime token provided in the webhook payload for API calls via context.Client. Use the helper to work with module data:
sdk.Register("case.created", "v1", async (payload, context) =>
{
var projectId = payload["case"]["project_id"].ToString();
var customData = context.Endpoints.CustomData(projectId!);
var list = await customData.ListAsync("com.example.crm.contacts", "automation_records", new CustomDataListOptions
{
Limit = 10,
Filters = new Dictionary<string, object> { ["status"] = "active" }
});
await customData.CreateAsync("com.example.crm.contacts", "automation_records", new Dictionary<string, object>
{
["email"] = "lead@example.com",
["metadata"] = new Dictionary<string, object> { ["source"] = "webhook" }
});
return new { synced = list?.Data.Count ?? 0 };
});SLA Alert Stream
Inspect the SLA alert feed for an installation:
sdk.Register("workflow.sla_status", "v1", async (payload, context) =>
{
var projectId = payload["case"]["project_id"].ToString();
var slaClient = context.Endpoints.SlaEvents(projectId!);
var events = await slaClient.ListAsync(new SlaEventsListOptions
{
State = "imminent",
Limit = 5
});
if (events?.Data?.Count == 0)
{
return new { ok = true };
}
var first = events!.Data![0];
await context.Endpoints.LogEventAsync("sla.warning", new Dictionary<string, object>
{
["issue_id"] = first["issue_id"],
["state"] = first["state"]
});
return new { acknowledged = true };
});Configuration
Environment Variables
KIKET_WEBHOOK_SECRETโ Webhook HMAC secret for signature verificationKIKET_WORKSPACE_TOKENโ Workspace token for API authenticationKIKET_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)
Manifest File
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: 5000API Reference
KiketSDK
Main SDK class for building extensions.
var sdk = new KiketSDK(new SDKConfig
{
WebhookSecret = "...",
WorkspaceToken = "...",
BaseUrl = "...",
Settings = new Dictionary<string, object>(),
ExtensionId = "...",
ExtensionVersion = "...",
ManifestPath = "...",
AutoEnvSecrets = true,
TelemetryEnabled = true,
FeedbackHook = record => { /* ... */ },
TelemetryUrl = "..."
});Methods:
sdk.Register(string event, string version, WebhookHandler handler)โ Register a webhook handlersdk.Run(string host = "127.0.0.1", int port = 8000)โ Start the ASP.NET Core serverawait sdk.StopAsync()โ Stop the server
HandlerContext
Context passed to webhook handlers:
public class HandlerContext
{
public string Event { get; }
public string EventVersion { get; }
public Dictionary<string, string> Headers { get; }
public KiketClient Client { get; }
public ExtensionEndpoints Endpoints { get; }
public Dictionary<string, object> Settings { get; }
public string? ExtensionId { get; }
public string? ExtensionVersion { get; }
public ExtensionSecretManager Secrets { get; }
public string? Secret(string key); // Secret helper with payload-first fallback
}Response Helpers
Use the ExtensionResponse class to build properly formatted responses:
using Kiket.SDK.Responses;
// Simple allow
return ExtensionResponse.Allow().Build();
// Allow with message and data
return ExtensionResponse.Allow()
.WithMessage("Successfully configured")
.WithData("routeId", 123)
.Build();
// Allow with output fields (displayed in configuration UI)
return ExtensionResponse.Allow()
.WithMessage("Mailjet configured successfully")
.WithData("routeId", route.Id)
.WithOutputField("inbound_email", route.Email)
.Build();
// Deny with error details
return ExtensionResponse.Deny("Invalid credentials")
.WithData("errorCode", "AUTH_FAILED")
.Build();
// Pending for async operations
return ExtensionResponse.Pending("Awaiting approval")
.WithData("jobId", "abc123")
.Build();Output fields are displayed in the extension configuration UI after setup, allowing extensions to expose generated data like email addresses, webhook URLs, or status information.
Secret Helper
The Secret() method provides a simple way to retrieve secrets with automatic fallback:
// Checks payload secrets first (per-org config), falls back to ENV
var slackToken = context.Secret("SLACK_BOT_TOKEN");
// Example usage
sdk.Register("case.created", "v1", async (payload, context) =>
{
var apiKey = context.Secret("API_KEY");
if (apiKey is null)
{
throw new InvalidOperationException("API_KEY not configured");
}
// Use apiKey...
return new { ok = true };
});The lookup order is:
- Payload secrets (per-org configuration from
payload["secrets"]) - Environment variables (extension defaults via
Environment.GetEnvironmentVariable())
This allows organizations to override extension defaults with their own credentials.
ExtensionEndpoints
High-level extension endpoints:
await context.Endpoints.LogEventAsync("event.name", data);
var metadata = await context.Endpoints.GetMetadataAsync();ExtensionSecretManager
Secret manager for CRUD operations:
var value = await context.Secrets.GetAsync("API_KEY");
await context.Secrets.SetAsync("API_KEY", "new-value");
await context.Secrets.DeleteAsync("API_KEY");
var keys = await context.Secrets.ListAsync();
await context.Secrets.RotateAsync("API_KEY", "new-value");Publishing to GitHub Packages
When you are ready to cut a release:
- Update the version in
Kiket.SDK.csproj. - Run the test suite (
dotnet test). - Build and pack:
dotnet build --configuration Release dotnet pack --configuration Release - Commit and tag the release:
git add Kiket.SDK/Kiket.SDK.csproj git commit -m "Bump .NET SDK to v0.x.y" git tag dotnet-v0.x.y git push --tags - GitHub Actions will automatically publish to GitHub Packages.
License
MIT
Rate-Limit Helper
Gate expensive automation against the current window:
sdk.Register("automation.dispatch", "v1", async (_payload, context) =>
{
var limits = await context.Endpoints.GetRateLimitAsync();
if (limits is not null && limits.Remaining < 5)
{
await context.Endpoints.LogEventAsync("rate_limited", new()
{
["remaining"] = limits.Remaining,
["reset_in"] = limits.ResetIn
});
return new { deferred = true };
}
// Continue with the heavy work
return new { ok = true };
});