Module: Tingee::Signature
- Defined in:
- lib/tingee/signature.rb
Overview
HMAC-SHA512 signing, per the official tingee-node SDK and verified live
(docs/tingee-api-reference.md §Auth). Outbound requests sign
timestamp + ":" + minified_body, where a bodyless request signs "{}" (not "")
— signing "" returns code 97. Inbound webhooks are verified over the RAW body
bytes, not a re-serialization (see #verify).
Class Method Summary collapse
- .generate(secret:, timestamp:, body:) ⇒ Object
-
.secure_compare(left, right) ⇒ Object
Constant-time comparison, hand-rolled so the gem needs no ActiveSupport.
-
.timestamp(now = Time.now) ⇒ Object
yyyyMMddHHmmssSSS in UTC+7.
-
.verify(secret:, timestamp:, raw_body:, signature:) ⇒ Object
Verify an inbound webhook signature.
Class Method Details
.generate(secret:, timestamp:, body:) ⇒ Object
18 19 20 21 22 |
# File 'lib/tingee/signature.rb', line 18 def generate(secret:, timestamp:, body:) # `.b`: hash raw bytes (silences the json 3.0 UTF-8/BINARY warning); the # digest of ASCII/UTF-8 bytes is unchanged either way. OpenSSL::HMAC.hexdigest("SHA512", secret, "#{}:#{body}".b) end |
.secure_compare(left, right) ⇒ Object
Constant-time comparison, hand-rolled so the gem needs no ActiveSupport.
36 37 38 39 40 41 42 |
# File 'lib/tingee/signature.rb', line 36 def secure_compare(left, right) return false unless left.bytesize == right.bytesize diff = 0 left.each_byte.with_index { |byte, i| diff |= byte ^ right.getbyte(i) } diff.zero? end |
.timestamp(now = Time.now) ⇒ Object
yyyyMMddHHmmssSSS in UTC+7. Server rejects >10 min clock drift (error 90).
14 15 16 |
# File 'lib/tingee/signature.rb', line 14 def (now = Time.now) now.getlocal("+07:00").strftime("%Y%m%d%H%M%S%L") end |
.verify(secret:, timestamp:, raw_body:, signature:) ⇒ Object
Verify an inbound webhook signature. Resolved against a real captured payment
webhook (2026-07-16): Tingee signs the RAW body bytes exactly as sent.
We hash raw_body verbatim — do NOT re-parse/re-serialize. Raw is both correct
and immune to Ruby-vs-JS number formatting (a re-serialized whole-number float
would render "250000.0" in Ruby vs "250000" in JS and break a valid signature).
The caller must pass the body exactly as received (Rails: request.raw_post),
never a re-encoded body.
31 32 33 |
# File 'lib/tingee/signature.rb', line 31 def verify(secret:, timestamp:, raw_body:, signature:) secure_compare(generate(secret:, timestamp:, body: raw_body), signature.to_s) end |