Hookd Ruby Client
Ruby client library for Hookd, a DNS/HTTP interaction server for security testing and debugging.
Installation
Add this line to your application's Gemfile:
gem 'hookd-client'
Or install it yourself as:
gem install hookd-client
Usage
Basic Example
require 'hookd'
require 'typhoeus'
# Initialize the client
client = Hookd::Client.new(
server: "https://hookd.example.com",
token: ENV['HOOKD_TOKEN']
)
# Register a new hook
hook = client.register
puts "DNS endpoint: #{hook.dns}"
puts "HTTP endpoint: #{hook.http}"
puts "HTTPS endpoint: #{hook.https}"
# Make a request to the HTTP endpoint to simulate an interaction
Typhoeus.get(hook.http)
# Poll for interactions (single hook)
interactions = client.poll(hook.id)
interactions.each do |interaction|
if interaction.dns?
puts "DNS query: #{interaction.data}"
elsif interaction.http?
puts "HTTP request: #{interaction.data}"
end
end
Batch Polling Example
When working with multiple hooks, use poll_batch for better performance:
require 'hookd'
require 'typhoeus'
# Initialize the client
client = Hookd::Client.new(
server: "https://hookd.example.com",
token: ENV['HOOKD_TOKEN']
)
# Register multiple hooks
puts "Registering 5 hooks..."
hooks = client.register(count: 5)
hook_ids = hooks.map(&:id)
puts "Created hooks: #{hook_ids.join(', ')}"
# Simulate some interactions...
puts "\nSimulating HTTP requests..."
hooks.each do |hook|
Typhoeus.get(hook.http)
puts " ✓ GET #{hook.http}"
end
# Batch poll all hooks at once (1 HTTP request instead of 5)
puts "Batch polling #{hook_ids.size} hooks..."
results = client.poll_batch(hook_ids)
# Display results
results.each do |hook_id, result|
if result[:error]
puts "❌ Hook #{hook_id}: #{result[:error]}"
else
interactions = result[:interactions]
puts "✅ Hook #{hook_id}: #{interactions.size} interaction(s)"
interactions.each do |interaction|
if interaction.dns?
puts " - DNS: #{interaction.data['qname']} (#{interaction.data['qtype']})"
elsif interaction.http?
puts " - HTTP: #{interaction.data['method']} #{interaction.data['path']}"
end
end
end
end
Configuration
The client requires two configuration parameters:
server: The Hookd server URL (e.g.,https://hookd.example.com)token: Authentication token for API access
API Reference
Hookd::Client
Main client class for interacting with the Hookd server.
#register(count: nil)
Register one or more hooks and get DNS/HTTP endpoints.
Single hook (default):
hook = client.register
# => #<Hookd::Hook id="abc123" dns="abc123.hookd.example.com" ...>
Multiple hooks:
hooks = client.register(count: 5)
# => [#<Hookd::Hook id="abc123" ...>, #<Hookd::Hook id="def456" ...>, ...]
Long-lived hook (survives restarts, for stored-XSS style detection):
hook = client.register(ttl: '7d', metadata: { target: 'acme', field: 'profile.bio' })
# => #<Hookd::Hook id="abc123" ...>
hook.expires_at # => "2025-10-08T10:30:00Z"
hook. # => {"target"=>"acme", "field"=>"profile.bio"}
Parameters:
count(Integer, optional) - Number of hooks to create (default: 1)ttl(String, optional) - Lifetime as a Go duration ("168h") or day count ("7d"); a value above the server's ephemeralhook_ttlregisters a durable long-lived hook. Omit for an ephemeral hook.metadata(Hash, optional) - Stored with the hook and echoed back on poll
Returns:
Hookd::Hookobject whencountis 1 or not specified- Array of
Hookd::Hookobjects whencount> 1
Raises:
ArgumentError- Invalid count parameterHookd::AuthenticationError- Authentication failedHookd::ServerError- Server error (5xx)Hookd::ConnectionError- Connection failed
#activity
List the long-lived hooks that currently have pending interactions, so you can
discover which fired without polling each one; drain the details with #poll.
client.activity.each do |a|
puts "#{a.hook.id} fired #{a.pending_count} time(s), meta=#{a.hook.}"
client.poll(a.hook.id)
end
# => [#<Hookd::HookActivity hook=abc123 pending=3>, ...]
Returns: Array of Hookd::HookActivity (empty when none fired or long-lived is disabled)
#poll(hook_id)
Poll for interactions captured by a single hook.
interactions = client.poll("abc123")
# => [#<Hookd::Interaction type="dns" ...>, ...]
Parameters:
hook_id(String) - The hook ID to poll
Returns: Array of Hookd::Interaction objects
Raises:
Hookd::AuthenticationError- Authentication failedHookd::NotFoundError- Hook not foundHookd::ServerError- Server error (5xx)Hookd::ConnectionError- Connection failed
#poll_batch(hook_ids)
Batch poll - Poll for interactions from multiple hooks in a single request.
# Register multiple hooks
hooks = client.register(count: 3)
hook_ids = hooks.map(&:id)
# Batch poll all hooks at once (1 HTTP request instead of 3)
results = client.poll_batch(hook_ids)
# => {
# "abc123" => { interactions: [...], error: nil },
# "def456" => { interactions: [...], error: nil },
# "ghi789" => { interactions: [], error: nil }
# }
# Process results
results.each do |hook_id, result|
if result[:error]
puts "Error for #{hook_id}: #{result[:error]}"
else
puts "Hook #{hook_id}: #{result[:interactions].size} interactions"
result[:interactions].each do |interaction|
puts " - #{interaction.type}: #{interaction.data}"
end
end
end
Parameters:
hook_ids(Array) - Array of hook IDs to poll
Returns: Hash mapping hook IDs to results
- Each result contains:
interactions(ArrayHookd::Interaction) - Array of interactionserror(String, nil) - Error message if hook not found
Raises:
ArgumentError- Invalid hook_ids (not an array or empty)Hookd::AuthenticationError- Authentication failedHookd::ServerError- Server error (5xx)Hookd::ConnectionError- Connection failed
Benefits:
- Performance: Reduced latency with single HTTP request
- Efficiency: Automatic connection reuse with HTTPX
- Atomic: Consistent snapshot of all hooks
#metrics
Get server metrics (requires authentication).
metrics = client.metrics
# => {"total_hooks" => 42, "total_interactions" => 1337, ...}
Returns: Hash with metrics data
Hookd::Hook
Represents a registered hook with endpoints.
Attributes:
id(String) - Unique hook identifierdns(String) - DNS endpointhttp(String) - HTTP endpointhttps(String) - HTTPS endpointcreated_at(String) - Creation timestampexpires_at(String, nil) - Expiry timestamp (long-lived hooks)metadata(Hash, nil) - Metadata attached at registration
Hookd::HookActivity
Represents a long-lived hook that has pending interactions (returned by #activity).
Attributes:
hook(Hookd::Hook) - The long-lived hook that firedpending_count(Integer) - Number of interactions awaiting polllast_interaction_at(String) - Timestamp of the most recent interaction
Hookd::Interaction
Represents a captured DNS or HTTP interaction.
Attributes:
type(String) - Interaction type ("dns" or "http")timestamp(String) - When the interaction was captureddata(Hash) - Interaction details
Methods:
#dns?- Returns true if this is a DNS interaction#http?- Returns true if this is an HTTP interaction
Error Handling
The client raises specific exceptions for different error conditions:
begin
hook = client.register
rescue Hookd::AuthenticationError
puts "Invalid token"
rescue Hookd::ConnectionError => e
puts "Connection failed: #{e.}"
rescue Hookd::ServerError => e
puts "Server error: #{e.}"
end
Exception hierarchy:
-
Hookd::Error(base class)Hookd::AuthenticationError- 401 UnauthorizedHookd::NotFoundError- 404 Not FoundHookd::ServerError- 5xx Server ErrorHookd::ConnectionError- Network/connection errors