API Reference

Webhooks

Receive real-time notifications when events happen in your Rebill account. Covers setup, events, payload format, signature verification, and auto-disable behaviour.

Last updated

Webhooks let your application receive real-time HTTP POST notifications when events happen in Rebill (for example when an invoice is paid, a quote is accepted, or a client is created). You register a URL, choose which events to subscribe to, and Rebill delivers a signed JSON payload to that URL.

Setting up a webhook

  1. 1

    Go to Settings > Developer > Webhooks

    Click Developer under Settings in the sidebar, then choose Webhooks in the section rail.
    settings webhooks
  2. 2

    Add a webhook

    Click "Add Webhook". Enter the URL that will receive events, an optional description, and select which events to subscribe to.
    settings webhooks add
  3. 3

    Copy the secret

    After creating the webhook, copy the signing secret. You will need this to verify that incoming requests are genuine. The secret is only shown once, so store it securely.
    settings webhooks secret

Available events

You can subscribe to any combination of these events:

EventDescription
invoice.createdA new invoice was created
invoice.updatedAn invoice was modified
invoice.sentAn invoice was sent to the client
invoice.paidAn invoice was fully paid
invoice.payment_receivedA payment was recorded (partial or full)
invoice.cancelledAn invoice was cancelled
invoice.deletedAn invoice was deleted
quote.createdA new quote was created
quote.updatedA quote was modified
quote.sentA quote was sent to the client
quote.acceptedA client accepted a quote
quote.declinedA client declined a quote
quote.expiredA quote passed its expiry date
quote.convertedA quote was converted to an invoice
quote.deletedA quote was deleted
client.createdA new client was added
client.updatedA client was modified
client.deletedA client was deleted
payment.receivedA payment was received via a payment gateway

Payload format

Every delivery is an HTTP POST with a JSON body:

POST https://your-app.example.com/webhook
Content-Type: application/json
X-Rebill-Event: invoice.paid
X-Rebill-Signature: t=1700000000,v1=ad527032...
X-Rebill-Delivery: evt_01HYZ...

Payloads are intentionally thin: the body is a small envelope identifying what happened, not the full entity. Call back into the Rebill API with the resource_id (using your API key) to fetch the current state of the resource:

{
  "id": "evt_01HYZQK3F9G8X2P7R4T6W9Y1Z0",
  "event": "invoice.paid",
  "resource_id": "abc123",
  "account_id": "acc_xyz",
  "occurred_at": "2026-03-15T10:22:00Z",
  "parent_id": ""
}
FieldTypeDescription
idstringUnique ID for this delivery, prefixed evt_
eventstringThe event type, e.g. invoice.paid
resource_idstringID of the entity that changed. Fetch it with the matching API endpoint (e.g. GET /invoice/:id)
account_idstringYour account ID
occurred_attimestampWhen the event occurred
parent_idstringPresent on some events as a hint, e.g. the invoice ID on a payment.received event. Omitted when not applicable

Verifying signatures

Every webhook delivery includes an X-Rebill-Signature header in the form t=,v1=, where is a Unix timestamp and is an HMAC-SHA256 hex digest. The signed message is the timestamp and the raw request body joined with a period: ".". Recompute it with your webhook secret and compare using a timing-safe comparison:

import crypto from "crypto";

function verify(body: string, secret: string, header: string): boolean {
  const match = header.match(/^t=(\d+),v1=([0-9a-f]+)$/);
  if (!match) return false;
  const [, timestamp, signature] = match;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${body}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex")
  );
}

Equivalent in Go:

func Verify(header string, body []byte, secret string) bool {
	parts := strings.SplitN(header, ",", 2)
	if len(parts) != 2 {
		return false
	}
	timestamp := strings.TrimPrefix(parts[0], "t=")
	signature := strings.TrimPrefix(parts[1], "v1=")

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(timestamp))
	mac.Write([]byte{'.'})
	mac.Write(body)
	expected := hex.EncodeToString(mac.Sum(nil))

	got, err := hex.DecodeString(signature)
	if err != nil {
		return false
	}
	want, _ := hex.DecodeString(expected)

	return hmac.Equal(got, want)
}

Always use a timing-safe comparison to prevent timing attacks. Reject signatures whose timestamp is too far from the current time to guard against replay.

Managing webhooks via the API

Everything configurable from Settings > Developer > Webhooks is also available as an API. Only creating a new webhook is premium-gated (see the note at the end of this article); listing, reading, updating, deleting, rotating a secret, sending a test, and viewing deliveries all work on any plan, so a downgraded account can still manage and monitor webhooks it already has.

List webhooks

GET /webhook

Returns every webhook registered on your account. The signing secret is not included; only create and rotate return it.

curl https://api.rebill.co.za/webhook \
  -H "Authorization: Bearer sk_your_secret_key"

Response:

{
  "webhooks": [
    {
      "id": "wh_001",
      "created": "2026-03-01T00:00:00Z",
      "url": "https://your-app.example.com/webhook",
      "description": "Production",
      "events": ["invoice.paid", "invoice.payment_received"],
      "active": true,
      "paused": false,
      "consecutive_failures": 0
    }
  ]
}

Create a webhook

POST /webhook
FieldRequiredTypeDescription
urlYesstringA valid URL to receive deliveries
eventsYesarrayOne to 50 event names from the table above
descriptionNostringFree-text label
activeNobooleanWhether the webhook is enabled (default: true)
curl -X POST https://api.rebill.co.za/webhook \
  -H "Authorization: Bearer sk_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhook",
    "description": "Production",
    "events": ["invoice.paid", "invoice.payment_received"]
  }'

Response (201 Created):

{
  "id": "wh_001",
  "url": "https://your-app.example.com/webhook",
  "description": "Production",
  "events": ["invoice.paid", "invoice.payment_received"],
  "active": true,
  "paused": false,
  "consecutive_failures": 0,
  "secret": "whsec_..."
}

The secret field is only ever returned here and from rotate; store it immediately. Free plan accounts receive 402 Payment Required. Each account may register up to 5 webhooks; exceeding that returns 400 Bad Request.

Get a webhook

GET /webhook/:id

Returns a single webhook, including its current secret (unlike the list endpoint).

Update a webhook

PUT /webhook/:id

Replaces a webhook's URL, description, events, and active flag. Accepts the same fields as create. Does not rotate the secret.

curl -X PUT https://api.rebill.co.za/webhook/wh_001 \
  -H "Authorization: Bearer sk_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhook",
    "events": ["invoice.paid"],
    "active": false
  }'

Delete a webhook

DELETE /webhook/:id

Removes the webhook. Its past delivery logs are left in place and age out on their own.

curl -X DELETE https://api.rebill.co.za/webhook/wh_001 \
  -H "Authorization: Bearer sk_your_secret_key"

Rotate the signing secret

POST /webhook/:id/rotate_secret

Generates a fresh secret and returns it once in plaintext. In-flight deliveries already signed with the old secret still verify until delivered, but store the new secret immediately: it is not retrievable again after this response.

curl -X POST https://api.rebill.co.za/webhook/wh_001/rotate_secret \
  -H "Authorization: Bearer sk_your_secret_key"

Response: the webhook object with a new secret field.

Send a test event

POST /webhook/:id/test

Enqueues a synthetic ping event to the webhook, useful for verifying your endpoint and signature handling end-to-end.

curl -X POST https://api.rebill.co.za/webhook/wh_001/test \
  -H "Authorization: Bearer sk_your_secret_key"

Response:

{
  "delivery_id": "evt_01HYZQK3F9G8X2P7R4T6W9Y1Z0"
}

List delivery attempts

GET /webhook/:id/deliveries

Returns the most recent delivery attempts for the webhook (up to 50, newest first), useful for debugging failed deliveries without leaving your own systems.

curl https://api.rebill.co.za/webhook/wh_001/deliveries \
  -H "Authorization: Bearer sk_your_secret_key"

Response:

{
  "deliveries": [
    {
      "id": "dlv_001",
      "created": "2026-03-15T10:22:01Z",
      "webhook_id": "wh_001",
      "event": "invoice.paid",
      "resource_id": "abc123",
      "status_code": 200,
      "success": true,
      "attempt": 1,
      "duration_ms": 184
    }
  ]
}

Retries and auto-disable

If your endpoint returns a non-2xx status code, Rebill retries the delivery. After 20 consecutive failures, the webhook is automatically paused to prevent further load on your server.

Paused webhooks show a warning in Settings > Developer > Webhooks. Click "Resume" to re-enable delivery. A ping event is sent immediately to verify your endpoint is back online.

Testing webhooks

Click "Test" next to any webhook in Settings to send a ping event. Use a tool like webhook.site or ngrok during development to inspect payloads.

Note

Each account can register up to 5 webhooks. Webhooks are a premium feature: free-plan accounts receive 402 Payment Required when creating a webhook, whether via the API or in Settings. Managing an existing webhook (list, read, update, delete, rotate secret, test, deliveries) is not plan-gated.

Was this article helpful?

Still need help?

Our support team is happy to help you get the most out of Rebill.

Contact support