Build and run Kiket extensions with a batteries-included, strongly-typed Ruby toolkit.
- 🔌 Webhook decorators – define handlers with
sdk.webhook("issue.created", version: "v1"). - 🔐 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 Sinatra 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 helper – call
/api/v1/ext/custom_data/...withcontext[:endpoints].custom_data(project_id)using the configured extension API key. - 📉 Rate-limit helper – inspect
/api/v1/ext/rate_limitbefore launching heavy automation bursts. - 🧱 Typed & documented – designed for Ruby 3.2+ with rich documentation.
- 📊 Telemetry & feedback hooks – capture handler duration/success metrics automatically.
gem install kiket-sdk# main.rb
require 'kiket_sdk'
sdk = KiketSDK.new(
webhook_secret: 'sh_123',
workspace_token: 'wk_test',
extension_id: 'com.example.marketing',
extension_version: '1.0.0'
)
# Register webhook handler (v1)
sdk.register('issue.created', version: 'v1') do |payload, context|
summary = payload['issue']['title']
puts "Event version: #{context[:event_version]}"
context[:endpoints].log_event('issue.created', summary: summary)
context[:secrets].set('WEBHOOK_TOKEN', 'abc123')
{ ok: true }
end
# Register webhook handler (v2)
sdk.register('issue.created', version: 'v2') do |payload, context|
summary = payload['issue']['title']
context[:endpoints].log_event('issue.created', summary: summary, schema: 'v2')
{ ok: true, version: context[:event_version] }
end
sdk.run!(host: '0.0.0.0', port: 8080)When your manifest includes custom_data.permissions, set extension_api_key (or the KIKET_EXTENSION_API_KEY environment variable) so outbound calls to the extension API include X-Kiket-API-Key:
sdk.register('issue.created', version: 'v1') do |payload, context|
project_id = payload.dig('issue', 'project_id')
custom_data = context[:endpoints].custom_data(project_id)
list = custom_data.list('com.example.crm.contacts', 'automation_records', limit: 10, filters: { status: 'active' })
custom_data.create('com.example.crm.contacts', 'automation_records', {
email: '[email protected]',
metadata: { source: 'webhook' }
})
{ synced: list['data'].size }
endYou can also query live SLA alerts from within webhook handlers:
sdk.register('workflow.sla_status', version: 'v1') do |payload, context|
project_id = payload.dig('issue', 'project_id')
sla_events = context[:endpoints].sla_events(project_id)
events = sla_events.list(state: 'imminent', limit: 5)
next { ok: true } if events['data'].empty?
first = events['data'].first
context[:endpoints].secrets # available if you need per-alert secrets
context[:endpoints].log_event('sla.warning', issue_id: first['issue_id'], state: first['state'])
{ acknowledged: true }
endKIKET_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.
sdk = KiketSDK.new(
webhook_secret: String,
workspace_token: String,
base_url: String,
settings: Hash,
extension_id: String,
extension_version: String,
manifest_path: String,
auto_env_secrets: Boolean,
telemetry_enabled: Boolean,
feedback_hook: Proc,
telemetry_url: String
)Methods:
sdk.register(event, version:, &handler)– Register a webhook handlersdk.webhook(event, version:)– Decorator for registering handlerssdk.run!(host:, port:)– Start the Sinatra server
Context hash passed to webhook handlers:
{
event: String,
event_version: String,
headers: Hash,
client: KiketSDK::Client,
endpoints: KiketSDK::Endpoints,
settings: Hash,
extension_id: String,
extension_version: String,
secrets: KiketSDK::Secrets
}The SDK includes test helpers:
require 'kiket_sdk'
require 'rack/test'
RSpec.describe 'My webhook handler' do
include Rack::Test::Methods
let(:sdk) do
KiketSDK.new(webhook_secret: 'test-secret')
end
def app
sdk
end
it 'handles issue.created event' do
sdk.register('issue.created', version: 'v1') do |payload, context|
{ processed: payload['issue']['id'] }
end
payload = { issue: { id: '123', title: 'Test Issue' } }
body = payload.to_json
sig_data = KiketSDK::Auth.generate_signature('test-secret', body)
post '/v/1/webhooks/issue.created',
body,
'CONTENT_TYPE' => 'application/json',
'HTTP_X_KIKET_SIGNATURE' => sig_data[:signature],
'HTTP_X_KIKET_TIMESTAMP' => sig_data[:timestamp]
expect(last_response.status).to eq(200)
expect(JSON.parse(last_response.body)['processed']).to eq('123')
end
endWhen you are ready to cut a release:
- Update the version in
kiket-sdk.gemspec. - Run the test suite (
bundle exec rspec) and linting (bundle exec rubocop). - Build gem:
gem build kiket-sdk.gemspec
- Commit and tag the release:
git add kiket-sdk.gemspec git commit -m "Bump Ruby SDK to v0.x.y" git tag ruby-v0.x.y git push --tags - GitHub Actions will automatically publish to GitHub Packages.
MIT
Before enqueueing expensive jobs, inspect the current extension window:
sandbox = sdk.register('automation.dispatch', version: 'v1') do |_payload, context|
limits = context[:endpoints].rate_limit
if limits['remaining'] < 5
context[:endpoints].notify(
'Rate limit warning',
"Only #{limits['remaining']} requests remain (reset in #{limits['reset_in']}s)",
'warning'
)
next({ deferred: true })
end
# Continue with the heavy work
{ ok: true }
end