Ruby
Kiket Ruby SDK.
Build and run Kiket extensions with a batteries-included, strongly-typed Ruby toolkit.
Features
- ๐ Webhook decorators โ define handlers with
sdk.webhook("case.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.
Quickstart
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('case.created', version: 'v1') do |payload, context|
summary = payload['case']['title']
puts "Event version: #{context[:event_version]}"
context[:endpoints].log_event('case.created', summary: summary)
context[:secrets].set('WEBHOOK_TOKEN', 'abc123')
{ ok: true }
end
# Register webhook handler (v2)
sdk.register('case.created', version: 'v2') do |payload, context|
summary = payload['case']['title']
context[:endpoints].log_event('case.created', summary: summary, schema: 'v2')
{ ok: true, version: context[:event_version] }
end
sdk.run!(host: '0.0.0.0', port: 8080)Custom Data Client
When your manifest includes custom_data.permissions, the SDK automatically uses the runtime token provided in the webhook payload for API calls via context[:client]:
sdk.register('case.created', version: 'v1') do |payload, context|
project_id = payload.dig('case', '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: 'lead@example.com',
metadata: { source: 'webhook' }
})
{ synced: list['data'].size }
endSLA Alert Stream
You can also query live SLA alerts from within webhook handlers:
sdk.register('workflow.sla_status', version: 'v1') do |payload, context|
project_id = payload.dig('case', '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 }
endConfiguration
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.
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
Handler Context
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,
secret: Proc, # Secret helper with payload-first fallback
auth: {
runtime_token: String, # Per-invocation API token
token_type: String, # Typically "runtime"
expires_at: String, # Token expiration timestamp
scopes: Array<String> # Granted API scopes
}
}Secret Helper
The secret proc provides a simple way to retrieve secrets with automatic fallback:
# Checks payload secrets first (per-org config), falls back to ENV
slack_token = context[:secret].call("SLACK_BOT_TOKEN")
# Example usage
sdk.register('case.created', version: 'v1') do |payload, context|
api_key = context[:secret].call("API_KEY")
raise "API_KEY not configured" unless api_key
# Use api_key...
{ ok: true }
endThe lookup order is:
- Payload secrets (per-org configuration from
payload["secrets"]) - Environment variables (extension defaults via
ENV)
This allows organizations to override extension defaults with their own credentials.
Runtime Token Authentication
The Kiket platform sends a per-invocation runtime_token in each webhook payload. This token is automatically extracted and used for all API calls made through context[:client] and context[:endpoints]. The runtime token provides organization-scoped access and is preferred over static tokens.
sdk.register('case.created', version: 'v1') do |payload, context|
# Access authentication context
puts "Token expires at: #{context[:auth][:expires_at]}"
puts "Scopes: #{context[:auth][:scopes].join(', ')}"
# API calls automatically use the runtime token
context[:endpoints].log_event('processed', { ok: true })
{ ok: true }
endScope Checking
Extensions can declare required scopes when registering handlers. The SDK will automatically check scopes before invoking the handler and return a 403 error if insufficient.
# Declare required scopes at registration time
sdk.register('case.created', version: 'v1', required_scopes: ['cases.read', 'cases.write']) do |payload, context|
# Handler only executes if scopes are present
context[:endpoints].log_event('case.processed', { id: payload['case']['id'] })
{ ok: true }
end
# Or check scopes dynamically within the handler
sdk.register('workflow.triggered', version: 'v1') do |payload, context|
# Raises KiketSDK::ScopeError if scopes are missing
context[:require_scopes].call('workflows.execute', 'custom_data.write')
# Continue with scope-protected operations
context[:endpoints].custom_data(project_id).create(...)
{ ok: true }
endResponse Helpers
The SDK provides helper methods for building properly formatted responses. These ensure your extension returns data in the format Kiket expects.
Basic Responses
# Allow (success) response
KiketSDK.allow(message: 'Operation completed')
# => { status: 'allow', message: 'Operation completed', metadata: {} }
# Deny response
KiketSDK.deny(message: 'Invalid credentials')
# => { status: 'deny', message: 'Invalid credentials', metadata: {} }
# Pending response (for async operations)
KiketSDK.pending(message: 'Awaiting approval')
# => { status: 'pending', message: 'Awaiting approval', metadata: {} }Output Fields
Output fields allow your extension to expose generated data (like email addresses, URLs, or status info) in the extension configuration UI. Users can see and copy these values after setup.
sdk.register('extension.testConnection', version: 'v1') do |payload, context|
# Perform setup (e.g., create Mailjet parse route)
route = create_parse_route(webhook_url)
# Return success with output fields
KiketSDK.allow(
message: 'Successfully configured',
data: { route_id: route.id },
output_fields: {
'inbound_email' => route.email,
'connection_status' => 'active'
}
)
endOutput fields are declared in your extension manifest:
extension:
output_fields:
inbound_email:
label: Inbound Email
description: Send emails to this address to create cases
type: copyable
icon: envelope
connection_status:
label: Connection Status
type: badgeSupported output field types:
copyable- Text field with copy button (default)url- Clickable URL with copy buttoncode- Monospace code blockbadge- Status indicator with colorstatus- Rich status with icon
Testing
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 case.created event' do
sdk.register('case.created', version: 'v1') do |payload, context|
{ processed: payload['case']['id'] }
end
payload = { case: { id: '123', title: 'Test Issue' } }
body = payload.to_json
sig_data = KiketSDK::Auth.generate_signature('test-secret', body)
post '/v/1/webhooks/case.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
endPublishing to GitHub Packages
When 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.
License
MIT
Rate-Limit Helper
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