Verify a Sume Avatar Video Webhook in Ruby (HMAC-SHA256)
A Ruby verifier for Sume job webhooks: sign timestamp.raw_body with HMAC-SHA256, accept a rotation header, refuse an empty secret, and dedupe on job_id.
A finished Avatar Video job can call your server. To trust the call, recompute the HMAC-SHA256 of the timestamp, a dot and the raw request body, then compare it with the x-sume-webhook-signature header. The Ruby below does that, refuses an empty secret and accepts a header carrying several signatures during secret rotation.
The scheme is from Sume's webhooks docs, read on 2026-10-05: the headers are x-sume-webhook-timestamp and x-sume-webhook-signature, formatted sume-v1=<hex>, with a 5-minute tolerance.
What does the verifier do?
Pass the exact raw bytes of the body, not a re-serialized hash, because the signature covers them. The function returns false for an empty secret, a malformed or stale timestamp, a tampered body or a non-matching signature. The header can hold comma-separated entries, and any matching entry is accepted. The comparison is constant-time.
require "openssl"
def same_bytes?(a, b)
return false unless a.bytesize == b.bytesize
a.bytes.zip(b.bytes).reduce(0) { |acc, (x, y)| acc | (x ^ y) }.zero?
end
def verify_sume_webhook(raw_body, timestamp, header, secret, tolerance: 300)
return false if secret.to_s.empty?
ts = Integer(timestamp, 10)
return false if (Time.now.to_i - ts).abs > tolerance
digest = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{ts}.#{raw_body}")
expected = "sume-v1=#{digest}"
matched = false
header.to_s.split(",").each do |entry|
candidate = entry.strip
matched = true if candidate.start_with?("sume-v1=") && same_bytes?(candidate, expected)
end
matched
rescue ArgumentError, TypeError
false
endWhat else should the handler do?
Respond with a 2xx quickly. Sume's docs describe up to 10 attempts at 30-second spacing with a 10-second timeout, so a slow handler is retried. Use job_id as the idempotency key, so a repeated delivery does not render or charge twice on your side.
Then fetch the result from the job with the endpoints in Jobs and results instead of trusting fields in the webhook alone. Webhooks are an alternative to polling job status.
How do you check it?
Sign a sample body with a test secret, check it returns true, then flip one byte of the body and check it returns false. Also try an empty secret, which should always be false.
Sources
Related posts
More in Developers
- Vertical 9:16 text-to-video API: a 12-second Wan 3.0 clip, priced
A 12-second 9:16 text-to-video request on Sume with Wan 3.0 costs $0.75 at 480p, $1.50 at 720p and $3.00 at 1080p. Full body and the other models that fit.
- OpenRouter video lists 4-8 second clips; Sume model ranges run 2-30s
OpenRouter's video guide shows typical 4-8 second durations. Sume validates duration per model, from 2-30s on Wan 3.0 to 3-10s on Omni 1.1. See the table.
- Video edit on Sume: video_url cannot ride with image fields
For gemini-omni-flash-1.1 video-to-video edit, video_url is the source, not a reference. Mixing it with image_url or reference lists fails. Fix and examples.
- Video filter 400 video_filter_ops_empty: send one op or a filtergraph
Video filter 1.0 needs at least one op in ops[] (max 8) or a non-empty filtergraph. POST /v1/video-filter/check returns diagnostics for free before you pay.
Written by Sume