Module: ScreenshotFreeAPI::Webhooks

Defined in:
lib/screenshotfreeapi/webhooks.rb

Overview

Utilities for verifying incoming ScreenshotFreeAPI webhook signatures.

The API signs webhook payloads with HMAC-SHA256 using your webhook secret. The signature is delivered in the ‘X-ScreenshotFree-Signature` header as a hex digest.

Example (Rack / Rails):

raw_body  = request.body.read
signature = request.headers["X-ScreenshotFree-Signature"]

unless ScreenshotFreeAPI::Webhooks.verify_signature(raw_body, signature, ENV["WEBHOOK_SECRET"])
  render plain: "Bad signature", status: :forbidden and return
end

Class Method Summary collapse

Class Method Details

.construct_event(raw_body, signature, secret) ⇒ Hash

Parse a verified webhook payload into a plain hash.

Raises ArgumentError if the signature is invalid.

Parameters:

  • raw_body (String)

    Raw request body

  • signature (String)

    X-ScreenshotFree-Signature header value

  • secret (String)

    Webhook signing secret

Returns:

  • (Hash)

    Parsed event payload



47
48
49
50
51
52
53
54
# File 'lib/screenshotfreeapi/webhooks.rb', line 47

def self.construct_event(raw_body, signature, secret)
  unless verify_signature(raw_body, signature, secret)
    raise ArgumentError, "Invalid webhook signature"
  end

  require "json"
  JSON.parse(raw_body)
end

.verify_signature(raw_body, signature, secret) ⇒ Boolean

Verify that a webhook payload was signed by the ScreenshotFreeAPI.

Uses a constant-time comparison to prevent timing attacks.

Parameters:

  • raw_body (String)

    Raw request body bytes (before any parsing)

  • signature (String)

    Value of the ‘X-ScreenshotFree-Signature` header

  • secret (String)

    Your webhook signing secret

Returns:

  • (Boolean)

    ‘true` if the signature is valid, `false` otherwise



30
31
32
33
34
35
36
# File 'lib/screenshotfreeapi/webhooks.rb', line 30

def self.verify_signature(raw_body, signature, secret)
  return false if raw_body.nil? || signature.nil? || secret.nil?
  return false if signature.empty? || secret.empty?

  expected = OpenSSL::HMAC.hexdigest("SHA256", secret.to_s, raw_body.to_s)
  secure_compare(expected, signature.to_s)
end