invoq Ruby SDK

Accept stablecoin payments with invoq from Ruby server code. This SDK wraps invoq server APIs and verifies signed webhooks.

Use this gem only on your server. It handles secret keys and should not be bundled into browser code.

Installation

Install the gem:

gem install invoq

Or add it to a Gemfile:

gem "invoq"

Requires Ruby 2.6 or newer.

Get your keys

  1. Sign in to the invoq dashboard and create a project.
  2. On the API keys page, create a secret key. Test keys start with sk_test_, live keys with sk_live_. The key mode determines whether invoices are test or live.
  3. In your project's webhooks settings, save your webhook URL. The webhook secret (whsec_...) for that mode is shown once, when you first enable the webhook. Store it right away. Webhook URLs must be public HTTPS URLs.

Add both to your server environment:

INVOQ_SECRET_KEY=sk_test_...
INVOQ_WEBHOOK_SECRET=whsec_...

Start with test keys. Switch to the live key and live webhook secret when you go to production.

Create a client

require "invoq"

invoq = Invoq.new(ENV.fetch("INVOQ_SECRET_KEY"))

Default production API origin:

https://api.invoq.money

Override the API origin and request timeout during development:

invoq = Invoq.new(
  ENV.fetch("INVOQ_SECRET_KEY"),
  api_origin: "http://localhost:8787",
  timeout_ms: 10_000
)

api_origin must be an absolute http or https origin with no path, query, hash, username, or password. The SDK appends /v1/... resource paths.

Requests time out after 10 seconds by default. Pass timeout_ms to change the timeout. timeout_ms must be a positive integer in milliseconds.

Invoices

Create an invoice:

invoice = invoq.invoices.create(
  amount: "129",
  currency: "USD",
  description: "SaaS boilerplate",
  reference_id: "order_1234",
  return_url: "https://merchant.example/thanks"
)

invoice_id = invoice.fetch("id")
checkout_url = "https://pay.invoq.money/#{invoice_id}"

Notes:

  • Use a server-side amount. Do not trust client-supplied amounts.
  • amount is a decimal USD string from "0.01" to "999.99" with up to 2 decimal places, such as "129" or "129.99".
  • currency is optional and defaults to "USD".
  • Use a stable, non-empty reference_id to map invoice.paid webhooks back to your order. Creating again with the same reference_id and invoice terms returns the existing invoice; different terms fail with a 409 reference_id_conflict API error.
  • If you fulfill by invoice ID instead of reference_id, store invoice_id with your order when you create the invoice.
  • Omit return_url to use the project's default return URL. Pass nil to send JSON null and create the invoice without a return URL. On reference_id retries, pass return_url explicitly when you need to assert a specific value.
  • description and reference_id must be strings when present.

Get an invoice:

invoice = invoq.invoices.get("inv_123")

invoices.get returns the public invoice shape used by hosted checkout. It includes fields such as amount_paid, amount_due, payment_status, project, deposit_address, monitoring_ends_at, and direct_onchain_rails, but does not include reference_id. Use the create response or invoice.paid webhook when you need your merchant reference_id.

Create a test payment:

paid_invoice = invoq.invoices.create_test_payment(
  "inv_123",
  amount: "129",
  reference_id: "test_payment_001"
)

Test invoices cannot receive real funds. Simulate payments from your server instead.

create_test_payment only works for invoices created with a sk_test_ key. Partial amounts are allowed and produce partially_paid; when payments reach the invoice amount, invoq sends a signed invoice.paid webhook to your test webhook URL.

reference_id is optional for test payments. Omit it when unset; do not pass nil.

To receive webhooks on your machine, expose your local server with an HTTPS tunnel such as ngrok or cloudflared and save the tunnel URL as your test webhook URL in the dashboard. The dashboard can also send a signed webhook.ping to check connectivity.

Each invoice method returns the response data object directly as a Ruby hash.

Hosted checkout page

Every invoice also has a hosted checkout page at:

https://pay.invoq.money/<invoice id>

Share the link or redirect to it when an in-page checkout modal is not a fit.

Inputs and responses

The SDK checks that amount values and invoice_id arguments are non-empty strings before sending requests. The invoq API validates the amount format, range, and currency.

Leave unset optional fields out of the request hash. When you include description or reference_id, pass a string. return_url can be a string or nil.

Amounts in responses are normalized to 4 decimal places: create with "129" and the invoice returns amount: "129.0000". Compare amounts numerically, not as strings. amount_due is derived as max(amount - amount_paid, 0) and uses the same 18-decimal scale as amount_paid.

Webhooks

Pass the raw request body to verify_webhook. Do not parse JSON and encode it again before verification.

This Rack example returns [status, headers, body]. In Rails, use request.raw_post and request.get_header("HTTP_INVOQ_SIGNATURE"); in Sinatra or another Ruby framework, use the framework's raw request body and exposed invoq-signature or HTTP_INVOQ_SIGNATURE header.

def handle_invoq_webhook(env)
  raw_body = env.fetch("rack.input").read

  begin
    event = Invoq.verify_webhook(
      raw_body,
      { "invoq-signature" => env["HTTP_INVOQ_SIGNATURE"] },
      ENV.fetch("INVOQ_WEBHOOK_SECRET")
    )
  rescue Invoq::SignatureVerificationError
    return [
      400,
      { "content-type" => "application/json" },
      ['{"error":"invalid signature"}']
    ]
  end

  if Invoq.invoice_paid?(event)
    invoice = event.fetch("data").fetch("invoice")
    fulfillment_key = invoice["reference_id"] || invoice.fetch("id")

    # Fulfill the order for fulfillment_key idempotently.
  end

  [
    200,
    { "content-type" => "application/json" },
    ['{"received":true}']
  ]
end

Use invoice.paid webhooks to fulfill orders on your server. Browser checkout results are only for updating the customer experience; do not fulfill orders from browser results.

When Invoq.invoice_paid?(event) is true, the invoice is ready for automatic fulfillment; use the invoice reference_id or a stored invoice id to find and fulfill your order. A review_required invoice does not emit an invoice.paid webhook yet. If checkout reports review_required, show a pending-review state and wait for a later invoice.paid webhook after review is approved.

Important:

  • Pass the exact raw request body string received by your Ruby framework.
  • Pass the invoq-signature header.
  • verify_webhook does not require Invoq.new(...) or your invoq API secret key.
  • Use your webhook secret (whsec_...), not INVOQ_SECRET_KEY.
  • Make fulfillment idempotent. Retried webhook deliveries can send the same event more than once.
  • Respond with a 2xx quickly. Any other status counts as a failed delivery. Transient failures such as timeouts, 429, and 5xx responses are retried; other 4xx responses are not.

Invoq.invoice_paid? accepts fulfillable invoice.paid events whose invoice status is paid, settling, or settled; it rejects review_required.

Webhook verification failures raise Invoq::SignatureVerificationError. The SDK allows a 5-minute timestamp tolerance. The signature header is:

invoq-signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">

Errors

begin
  invoq.invoices.create(amount: "0.001", currency: "USD")
rescue Invoq::ApiError => error
  warn error.status
  warn error.code
  warn error.fields
  warn error.meta
  warn error.payload
rescue Invoq::Error
  raise
end

Non-2xx API responses raise Invoq::ApiError with status, code, fields, meta, and the raw payload.

Connection failures, timeouts, invalid input, and response parse failures raise Invoq::Error. Timed-out invoice creation is safe to retry with the same reference_id.

Webhook verification failures raise Invoq::SignatureVerificationError with one of these codes:

missing_signature
invalid_signature_header
timestamp_outside_tolerance
signature_mismatch
invalid_payload

Development

bundle exec rake test

License

Licensed under the MIT license.