helo-email-sdk
Helo API
Installation
Add the gem to your Gemfile:
gem "helo-email-sdk"
Then run:
bundle install
Configuration
Configure the SDK once, at boot:
require "helo-email-sdk"
Helo.configure do |config|
config.api_key = ENV.fetch("HELO_API_KEY")
config.base_url = "https://api.helohq.com" # optional, this is the default
end
Usage
Each API is a class with class-level methods. Responses come back as model objects with typed attribute readers.
result = Helo::Channels.list
Methods that take parameters accept a hash:
result = Helo::Channels.list(limit: 10)
See the API docs for every method, with a runnable example each.
Errors
Failed requests raise Helo::APIError:
begin
Helo::Channels.list
rescue Helo::APIError => e
e.code # HTTP status
e.detail # human-readable detail from the API
e.errors # field-level validation errors, when present
end
Webhook signature verification
Webhook deliveries are signed with the endpoint's signing key. Verify every delivery before acting on it, against the raw request body — parsing and re-serializing the JSON changes the bytes and the signature will not match.
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def create
Helo::WebhookSignatures.verify!(
request.headers["X-Helo-Webhook-Signature"],
request.raw_post, # raw body, exactly as received
ENV.fetch("HELO_WEBHOOK_SIGNING_KEY")
)
event = JSON.parse(request.raw_post)
# ... handle the event, then acknowledge quickly
head :no_content
rescue Helo::WebhookSignatures::Error
head :bad_request
end
end
verify! returns true when the signature is valid and raises otherwise. Each rejection has
its own class, so a stale delivery can be treated differently from a genuinely bad one:
| Exception | Meaning |
|---|---|
MalformedHeaderError |
The header was not in the expected format |
UnsupportedVersionError |
The delivery used a signing scheme this SDK version cannot verify — upgrade the gem |
TimestampSkewError |
Correctly signed, but too old to accept — possible replay, or clock drift |
SignatureMismatchError |
Wrong signing key, or the body was modified in transit |
All four inherit from Helo::WebhookSignatures::Error, so rescue that one class
to catch any rejection. If you only want a boolean, use valid? instead:
if Helo::WebhookSignatures.valid?(signature_header, raw_body, signing_key)
# ...
end
The signature header may carry several versions at once (t=...,v1=...,v2=...) while a new
signing scheme is being rolled out. This SDK verifies against the newest version it supports
(SUPPORTED_VERSIONS) and ignores elements it does not recognize, so a rollout will not break
this integration.
To compute a signature yourself — signing a fixture in tests, for example — use
Helo::WebhookSignatures.generate(payload, signing_key, timestamp).