Loading
Loading
Developer docs
Subscribe an HTTPS endpoint to live events from your shop — appointments, invoices, payments, warranties. Each delivery is HMAC-SHA256 signed.
Every event is wrapped in the same envelope. Thedata field varies by event type.
{
"event": "invoice.paid",
"shopId": "01234567-89ab-cdef-0123-456789abcdef",
"timestamp": "2026-04-26T14:23:11.000Z",
"data": {
"invoiceId": "01234567-...",
"customerId": "01234567-...",
"totalCents": 84900,
"paidAt": "2026-04-26T14:23:11.000Z"
}
}Language: jsonHeaders on every request:
Content-Type: application/json
User-Agent: SalesThumb-Webhook/1.0
X-SalesThumb-Event: invoice.paid
X-SalesThumb-Signature: sha256=<hex digest>
X-SalesThumb-Delivery: <unique uuid per attempt>Language: textVerify the signature before trusting a payload. The signature isHMAC-SHA256(secret, raw_body). Compare in constant time.
Node.js
import crypto from "node:crypto";
export function verifyWebhook(secret, header, rawBody) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const provided = header.replace(/^sha256=/, "");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(provided, "hex"),
);
}Language: javascriptPython
import hmac, hashlib
def verify_webhook(secret: str, header: str, raw_body: bytes) -> bool:
expected = hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
provided = header.replace("sha256=", "", 1)
return hmac.compare_digest(expected, provided)Language: pythonRuby
require "openssl"
def verify_webhook(secret, header, raw_body)
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, raw_body)
provided = header.sub(/^sha256=/, "")
Rack::Utils.secure_compare(expected, provided)
endLanguage: rubyAll event types you can subscribe to. More are added as new modules ship — see /changelog for additions.
appointment.created
A new appointment was booked.
appointment.confirmed
An appointment moved to CONFIRMED status.
appointment.checked_in
Customer checked in for an appointment.
appointment.completed
An appointment was marked complete.
appointment.canceled
An appointment was canceled or no-showed.
quote.sent
A quote was sent to a customer.
quote.accepted
A customer accepted a quote.
invoice.created
A new invoice was created.
invoice.sent
An invoice was sent to a customer.
invoice.paid
An invoice was paid in full.
payment.received
A payment was recorded against an invoice.
payment.refunded
A payment was fully or partially refunded.
warranty.registered
A warranty was registered for an install.
warranty.claim_filed
A customer filed a warranty claim.
customer.created
A new customer record was created.
The platform considers any non-2xx response a failure. Failures are recorded but not retried automatically yet— this is on the immediate roadmap (exponential backoff over ~24h). For now, your endpoint should be idempotent and queue work internally if it can't process synchronously.
After 10 consecutive failures, the subscription is auto-disabledand you'll be notified by email. Re-enable it from the dashboard once you've fixed the endpoint.
Timeout: 10 seconds. Long-running work should ack the webhook quickly (return 200 immediately) and process asynchronously.
X-SalesThumb-Signature on every request before trusting the payload.X-SalesThumb-Delivery as an idempotency key — store it and skip duplicates.A documented public REST/RPC API is on the roadmap. For automation today, use webhooks (push) for events you want to react to and Zapier (pull-style triggers) for tools that don't accept webhooks directly. CSV export covers bulk reporting. Email info@roffik.com if you have a use case the webhooks don't cover and we'll prioritize accordingly.
No — outbound webhooks are unlimited on every plan. The only thing we measure is consecutive failures (auto-disable kicks in at 10 in a row to protect your endpoint).
Each subscription is scoped to a single shop. If you operate multiple shops on SalesThumb, create one subscription per shop or have your endpoint distinguish based on the shopId in the payload envelope.
From the subscription detail in the app. Rotation invalidates the old secret immediately, so deploy your secret update first, then rotate.
HMAC-SHA256 with a 32-byte secret is industry-standard for webhook authenticity (major payment processors, GitHub, Twilio all use it). IP allowlisting is on the roadmap for shops that want defense-in-depth.
Create your first subscription in 30 seconds.
More developer docs