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
- Sign in to the invoq dashboard and create a project.
- On the API keys page, create a secret key. Test keys start with
sk_test_, live keys withsk_live_. The key mode determines whether invoices are test or live. - 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.
amountis a decimal USD string from"0.01"to"999.99"with up to 2 decimal places, such as"129"or"129.99".currencyis optional and defaults to"USD".- Use a stable, non-empty
reference_idto mapinvoice.paidwebhooks back to your order. Creating again with the samereference_idand invoice terms returns the existing invoice; different terms fail with a409 reference_id_conflictAPI error. - If you fulfill by invoice ID instead of
reference_id, storeinvoice_idwith your order when you create the invoice. - Omit
return_urlto use the project's default return URL. Passnilto send JSONnulland create the invoice without a return URL. Onreference_idretries, passreturn_urlexplicitly when you need to assert a specific value. descriptionandreference_idmust 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-signatureheader. verify_webhookdoes not requireInvoq.new(...)or your invoq API secret key.- Use your webhook secret (
whsec_...), notINVOQ_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, and5xxresponses are retried; other4xxresponses 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.
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.