Skip to Content
DevelopersWebhooks

Webhooks

Receive real-time notifications when events occur in your Rebased account.

How Webhooks Work

When something happens in Rebased (e.g., an invoice is created), we send an HTTP POST request to your specified URL with details about the event. Your server can then process this data automatically.

Creating a Webhook

  1. Go to Settings → Integrations → API Keys in your Rebased account
  2. Open the Webhooks tab (alongside API Keys and Usage & Limits)
  3. Click Create Webhook
  4. Enter a name and optional description
  5. Enter your destination URL (must be HTTPS in production)
  6. Select the events you want to receive
  7. Click Create Webhook

Your signing secret will be displayed once. Copy it immediately — you’ll need it to verify webhook signatures.

To change an existing endpoint’s URL, events, or name, click the pencil (Edit) icon on the webhook card. You can also add multiple webhook endpoints for the same business.

Only Business Admins (including the business owner) can create and edit webhooks. Team members without admin access cannot manage integrations.

Available Events

EventDescription
invoice.createdTriggered when an invoice is created
invoice.sentTriggered when an invoice is sent or marked as sent
invoice.updatedTriggered when an invoice is updated (header, line items, or contacts — not payments)
invoice.paidTriggered when an invoice becomes fully paid
bill.createdTriggered when a bill is created
bill.approvedTriggered when a bill is approved for payment
contact.createdTriggered when a client or supplier is created (via UI or API)
payment.receivedTriggered when a payment is allocated to an invoice (deposit, partial, or full)
quote.createdTriggered when a quote is created
quote.sentTriggered when a quote is sent to a recipient
quote.viewedTriggered when a quote share link is viewed by a recipient
quote.acceptedTriggered when a quote is accepted by a recipient or accepted on behalf by your team
quote.unacceptedTriggered when quote acceptance is withdrawn (e.g. undo acceptance or edit via new version)

Payment events (Stripe, bank, and manual)

Payment webhooks fire when a payment is allocated against an invoice in Rebased. The payment method does not change which events you receive — Stripe Checkout, bank reconciliation, and manually recorded payments all use the same events.

Situationpayment.receivedinvoice.paid
Deposit or partial paymentYesNo
Final payment that clears the balanceYesYes
Single payment that pays the invoice in fullYesYes

Recommended subscriptions

  • Use payment.received to react to any money applied to an invoice (including deposits and part payments).
  • Use invoice.paid when you only care that the invoice is fully settled.
  • Do not use invoice.updated to detect payments — that event is for invoice content changes only.

payment.received includes payment_method (for example stripe or bank_transfer), plus amount, invoice_id, and optional invoice_number / reference for matching back to your system.

Webhook delivery format

Each delivery is an HTTP POST to your destination URL with a JSON body and Rebased-specific headers. Webhooks are not authenticated with an API key — use the per-endpoint signing secret (see Verifying signatures) to confirm requests came from Rebased.

Request headers

HeaderDescription
Content-TypeAlways application/json
X-Rebased-EventEvent type (e.g. invoice.created, quote.viewed)
X-Rebased-DeliveryUnique delivery ID — use for deduplication
X-Rebased-TimestampUnix timestamp (seconds) used when signing
X-Rebased-SignatureHMAC-SHA256 signature, format sha256={hex}
User-AgentRebased-Webhooks/1.0

The event type is sent in X-Rebased-Event, not inside the JSON body.

Request body

The POST body is a flat JSON object with event-specific fields. Rebased does not wrap payloads in an outer envelope (no top-level data, event_type, or event_id in the body).

Always verify signatures against the raw request body bytes exactly as received. Do not re-serialize parsed JSON — key order and spacing must match what Rebased sent.

Event payloads

EventBody fields
invoice.createdinvoice_id, invoice_number, client_id, reference, total, status, created_at
invoice.sentinvoice_id, invoice_number, client_id, sent_at
invoice.updatedinvoice_id, invoice_number, client_id, reference, total, status, updated_at
invoice.paidinvoice_id, invoice_number, client_id, reference, amount_paid, paid_at (fully paid only)
bill.createdbill_id, bill_number, supplier_id, total, status, created_at
bill.approvedbill_id, bill_number, supplier_id, approved_at
contact.createdcontact_id, contact_type (client or supplier), name, email, created_at
payment.receivedpayment_id, invoice_id, amount, payment_method (stripe, bank_transfer, etc.), received_at, optional invoice_number, reference
quote.createdQuote fields below + created_at
quote.sentQuote fields below + sent_at
quote.viewedQuote fields below + viewed_at, view_count, last_viewed_at, viewer
quote.acceptedQuote fields below + recipients, optional accepting_contact, accepted_by, accepted_at, acceptance_comments, acceptance_order_number, interactive_selections, and when auto-conversion succeeds: invoice_id, invoice_number, invoice_status, invoice_admin_url, optional invoice_share_url. line_items and totals reflect accepted selections only (non-selected multiple-choice options and excluded optional items are omitted — same rules as quote→invoice conversion)
quote.unacceptedQuote fields below + previous_status, unaccepted_at, previous_acceptance

All quote webhook events also include:

FieldDescription
client_emailPrimary client email from the client record
client_address{ address_line1, address_line2, city, state, postcode, country }
quote_public_urlCustomer share link (/quotes/shared/{token}) — what the recipient sees
quote_admin_urlStaff link (/quotes/{quote_id}) — opens the quote in Rebased when logged in

quote_public_url is omitted if no share token exists yet. The public page requires a snapshot (created when the quote is sent). See docs/Quotes/PUBLIC_QUOTE_SHARING_SECURITY.md.

Monetary amounts are JSON strings (e.g. "1320.00").

Test deliveries

When you click Test in Settings → Integrations → Webhooks, Rebased sends:

  • Header: X-Rebased-Event: test
  • Body:
{ "event": "test", "timestamp": "2026-06-22T12:00:00.000000", "message": "This is a test webhook from Rebased", "business_id": "your-business-uuid" }

Quote example

Quote webhooks include header details and line items at the top level of the JSON body:

{ "quote_id": "quo_abc123", "quote_number": "QUO-1001", "title": "Website Build Quote", "client_id": "client_xyz789", "client_name": "A3D Industries", "client_email": "client@example.com", "client_address": { "address_line1": "123 Example St", "address_line2": null, "city": "Sydney", "state": "NSW", "postcode": "2000", "country": "Australia" }, "quote_public_url": "https://platform.rebased.app/quotes/shared/22222222-2222-2222-2222-222222222222", "quote_admin_url": "https://platform.rebased.app/quotes/quo_abc123", "status": "accepted", "issue_date": "2026-06-21", "valid_until": "2026-07-21", "currency": "AUD", "subtotal": "1200.00", "tax_amount": "120.00", "total": "1320.00", "line_items": [ { "id": "line_123", "item_code": "CONSULT", "title": "Consulting", "description": "Discovery and implementation planning", "quantity": 2, "unit_price": "600.00", "subtotal": "1200.00", "tax_rate": 10, "tax_amount": "120.00", "total": "1320.00", "optional": false } ], "accepted_by": "Aaron Smith", "accepted_at": "2026-06-21T03:00:00Z", "acceptance_comments": "Looks good", "acceptance_order_number": "PO-1234", "recipients": [ { "quote_contact_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "contact_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "contact_name": "Aaron Smith", "contact_email": "aaron@aussie3dfab.com.au", "contact_type": "to" } ], "accepting_contact": { "contact_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "contact_name": "Aaron Smith", "contact_email": "aaron@aussie3dfab.com.au", "contact_type": "to" }, "invoice_id": "550e8400-e29b-41d4-a716-446655440000", "invoice_number": "INV-1001", "invoice_status": "awaiting_payment", "invoice_admin_url": "https://platform.rebased.app/invoices/550e8400-e29b-41d4-a716-446655440000", "invoice_share_url": "https://platform.rebased.app/invoices/shared/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }

When a quote is accepted, Rebased auto-creates an invoice based on your Quotes → Settings → On Acceptance Rules preference:

  • Create draft invoiceinvoice_status is draft
  • Send approved invoiceinvoice_status is awaiting_payment; invoice_share_url is included when a public payment link exists

recipients lists every To/CC contact on the quote. accepting_contact identifies the recipient who accepted via a share link (same fields as quote.viewed’s viewer). accepted_by is the name recorded at acceptance and may differ if the signer typed a different name. accepting_contact is omitted when a staff member accepts on behalf of the client.

For interactive quotes, quote.accepted line_items only include what the client accepted:

  • Multiple-choice groups — only the selected option (non-selected siblings are omitted)
  • Optional items — omitted when the client excluded them
  • Editable quantities — use the quantity chosen at acceptance

interactive_selections is still included for integrators that need the raw selection map. Pre-acceptance events (quote.created, quote.sent, quote.viewed) continue to include the full quote catalogue of options.

Invoice fields are omitted if auto-conversion fails. Download the PDF with GET /invoices/{id}/pdf using your API key (invoices:read scope).

For quote.viewed, viewer is an object such as:

{ "contact_id": "uuid-or-null", "contact_name": "Aaron Smith", "contact_email": "aaron@example.com", "contact_type": "client" }

Verifying signatures

Every webhook request includes X-Rebased-Signature. Always verify signatures before processing webhooks.

Signing secret

When you create or rotate a webhook, Rebased shows a 64-character hexadecimal signing secret once. Store it securely and use it as a UTF-8 string (do not hex-decode it into binary).

After Rotate Secret, update your server immediately — the previous secret is invalidated.

Signature algorithm

Rebased computes:

HMAC-SHA256(signing_secret, "{timestamp}.{raw_body}")

Where:

  • timestamp is the value of X-Rebased-Timestamp (Unix seconds, as a string)
  • raw_body is the exact HTTP request body (bytes / string as received)
  • The header value is sha256={hex_digest} — strip the sha256= prefix before comparing

Optionally reject requests whose timestamp is too old (e.g. more than 5 minutes) to limit replay attacks.

Verification examples

import hmac import hashlib import time def verify_rebased_webhook(raw_body: bytes, headers: dict, secret: str, max_age_seconds: int = 300) -> bool: signature_header = headers.get("X-Rebased-Signature", "") timestamp = headers.get("X-Rebased-Timestamp", "") if not signature_header.startswith("sha256=") or not timestamp: return False received_sig = signature_header.removeprefix("sha256=") try: ts = int(timestamp) except ValueError: return False if abs(time.time() - ts) > max_age_seconds: return False signed_payload = f"{timestamp}.".encode() + raw_body expected = hmac.new(secret.encode("utf-8"), signed_payload, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, received_sig)
const crypto = require('crypto'); function verifyRebasedWebhook(rawBody, headers, secret, maxAgeSeconds = 300) { const signatureHeader = headers['x-rebased-signature'] || ''; const timestamp = headers['x-rebased-timestamp'] || ''; if (!signatureHeader.startsWith('sha256=') || !timestamp) { return false; } const receivedSig = signatureHeader.slice('sha256='.length); const ts = Number.parseInt(timestamp, 10); if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > maxAgeSeconds) { return false; } const signedPayload = Buffer.concat([ Buffer.from(`${timestamp}.`, 'utf8'), Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(rawBody, 'utf8'), ]); const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSig)); }
function verify_rebased_webhook(string $rawBody, array $headers, string $secret, int $maxAgeSeconds = 300): bool { $signatureHeader = $headers['X-Rebased-Signature'] ?? $headers['x-rebased-signature'] ?? ''; $timestamp = $headers['X-Rebased-Timestamp'] ?? $headers['x-rebased-timestamp'] ?? ''; if (!str_starts_with($signatureHeader, 'sha256=') || $timestamp === '') { return false; } $receivedSig = substr($signatureHeader, strlen('sha256=')); $ts = (int) $timestamp; if ($ts <= 0 || abs(time() - $ts) > $maxAgeSeconds) { return false; } $signedPayload = $timestamp . '.' . $rawBody; $expected = hash_hmac('sha256', $signedPayload, $secret); return hash_equals($expected, $receivedSig); }

Managing Webhooks

Testing a Webhook

Click the Test button on any webhook to send a test event. This helps verify your endpoint is receiving and processing webhooks correctly.

Pausing a Webhook

If you need to temporarily stop receiving events:

  1. Find the webhook in Settings → Integrations → Webhooks
  2. Click the Pause button

Paused webhooks won’t receive events until resumed.

Rotating the Signing Secret

If your signing secret is compromised:

  1. Find the webhook in Settings → Integrations → Webhooks
  2. Click Rotate Secret
  3. Copy the new secret immediately
  4. Update your server with the new secret

The old secret is immediately invalidated.

Deleting a Webhook

  1. Find the webhook in Settings → Integrations → Webhooks
  2. Click the Delete button
  3. Confirm the action

Deleted webhooks cannot be restored.

Retry Policy

If your endpoint returns an error (non-2xx status code) or times out, we’ll retry the delivery:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry24 hours

After 5 failed attempts, the delivery is marked as failed. You can manually retry failed deliveries from the webhook details page.

Best Practices

  • Respond quickly — Return a 2xx response within 30 seconds
  • Process asynchronously — Queue webhook data for background processing
  • Verify signatures — Always verify the X-Rebased-Signature header
  • Handle duplicates — Use the X-Rebased-Delivery header to deduplicate
  • Use HTTPS — Production endpoints must use HTTPS

Who Can Manage Webhooks

Webhooks can be created and managed by:

  • Business Admins — Full access to all integrations
  • Advisors — Can manage integrations for client businesses they have access to

Team members with other roles cannot create or view webhooks.

Last updated on