Skip to main content

Webhooks

&Open can send webhook events to notify your application when something happens in your account. Each webhook delivery is signed so you can verify it came from &Open and has not been tampered with.

Headers

Every webhook request includes these headers:

HeaderDescription
AndOpen-Webhook-Dispatch-TimestampUnix epoch seconds when the request was dispatched
AndOpen-Webhook-SignatureHMAC-SHA256 signature of the request
Content-TypeAlways application/vnd.api+json

Verifying signatures

To verify a webhook request:

  1. Extract the timestamp from AndOpen-Webhook-Dispatch-Timestamp
  2. Concatenate the timestamp and the raw request body with a dot: "{timestamp}.{body}"
  3. Compute the HMAC-SHA256 of that string using your webhook secret
  4. Compare the result to AndOpen-Webhook-Signature using a constant-time comparison

Replay protection

The dispatch timestamp enables stateless replay protection. After verifying the signature, check that the timestamp is within an acceptable window of the current time (e.g. 5 minutes). A captured request becomes useless after the tolerance window expires.

The dispatch timestamp is set fresh on each delivery attempt, so legitimate retries will not be rejected as stale.

Each webhook payload also includes the event ID, which you can use for stateful deduplication if needed.

Code Examples

require 'openssl'

timestamp = request.headers["AndOpen-Webhook-Dispatch-Timestamp"]
signature = request.headers["AndOpen-Webhook-Signature"]
body = request.body.read

# Reject stale requests (replay protection)
if (Time.now.to_i - timestamp.to_i).abs > 300
head :forbidden
return
end

# Verify signature
signed_content = "#{timestamp}.#{body}"
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, signed_content)

unless ActiveSupport::SecurityUtils.secure_compare(expected, signature)
head :forbidden
end