Skip to main content
Webhooks let Campfire notify your systems in real time when data changes — for example when an invoice is paid, a bill is created, or a journal entry is updated. Instead of polling the API, you register an HTTPS endpoint, subscribe it to the topics you care about, and Campfire sends a signed POST request to that endpoint every time a matching event occurs.
Managing webhooks — in the app or via the API — requires an admin user. Non-admin API keys receive a 403 from the webhook endpoints.

Setting up a webhook

You can create a webhook in the Campfire app or via the API.

In the app

1

Go to the Webhooks page

Go to Settings > Developer > Webhooks in your Campfire instance.
2

Create the webhook

Click New Webhook, enter the HTTPS URL of your endpoint, check Active, and select the topics you want to subscribe to. Topics are grouped by object (Invoices, Bills, Journal Entries, etc.) — you can select a whole group or individual topics.
3

Copy your signing secret

After saving, the webhook card shows an HMAC-SHA256 Secret. Click Show to reveal it and store it in your application’s secret store. You’ll use it to verify signatures on incoming deliveries.

Via the API

Create a webhook with a POST to the webhook endpoint. See our authentication guide for how to get an API key.
The response includes your signing secret (token) and the webhook’s uuid:
The signing secret is generated by Campfire and cannot be set or changed. To rotate it, delete the webhook and create a new one.

Topics

A webhook only receives events for the topics it is subscribed to. Topic names are exact strings — subscribe using the values below. Payment topics have special semantics for invoices and bills:
  • Invoice.payment / Bill.payment fires whenever the amount paid increases — including partial payments.
  • Invoice.paid / Bill.paid fires when the document transitions to fully paid. When that happens, both the .paid and .payment topics fire (as two separate deliveries), and no .updated event is sent for that change.

Payload

Every delivery is a POST with a JSON body containing four fields: Example delivery for Invoice.paid:

Request headers

Verifying the signature

Every delivery is signed with your webhook’s secret so you can verify it genuinely came from Campfire. The signed message is the delivery timestamp and the raw request body, joined by a single : character:
where timestamp is the value of the Campfire-Webhook-Request-Timestamp header. Campfire computes an HMAC-SHA256 digest of this message using your signing secret as the key, hex-encodes it, and sends it in the Campfire-Webhook-Signature-v1 header.
Compute the signature over the raw request body exactly as received — read it before your framework parses it, and never re-serialize the parsed JSON. Re-serializing can change whitespace and key order, which changes the signature.
To verify a delivery:
  1. Read the Campfire-Webhook-Request-Timestamp and Campfire-Webhook-Signature-v1 headers and the raw request body.
  2. Reject the delivery if the timestamp is too old — we recommend a tolerance of 5 minutes. This protects against replay attacks. (Retried deliveries are re-signed with a fresh timestamp, so a tolerance check will not reject legitimate retries.)
  3. Compute HMAC-SHA256 over "{timestamp}:{raw body}" using your signing secret, hex-encode it, and compare it to the signature header using a constant-time comparison.
In Express, use express.raw({ type: "application/json" }) (or capture req.rawBody) on your webhook route so the body is available unparsed. In Django/Flask/FastAPI, use request.body / request.get_data() / await request.body() respectively.
You can confirm your implementation against real deliveries: the delivery log for each event includes the exact request headers and body Campfire sent, including the computed signature.

Delivery and retries

  • Deliveries are POST requests. Respond with a 2xx status code to acknowledge receipt. Any 4xx or 5xx response is treated as a failure.
  • Failed deliveries are retried up to 5 times, roughly one minute apart. A 404 response is not retried.
  • Each retry is re-signed with a fresh timestamp and signature.
  • Respond quickly — acknowledge the delivery first and process the event asynchronously if your handling is slow.
  • Deliveries are not guaranteed to arrive in order, and retries mean your endpoint may occasionally receive the same event more than once. Make your handler idempotent — for example, treat the payload’s data as the latest state of the object rather than applying changes incrementally.

Delivery logs

Recent delivery attempts appear under Recent Events on the Webhooks settings page, where you can inspect each request and response. The same data is available via the API (most recent 25 events):
Each event includes the delivery status (PENDING, SUCCESS, or FAILURE), the topic, and an http object with the full request and response — URL, headers, body, and response status code.

Managing webhooks via the API

Notes:
  • {id} is the integer id field from the webhook object, not its uuid.
  • url and active are required on create and update.
  • topics is a list of {"name": "<topic>"} objects. Topic names must match the values in the topics table exactly.
  • Updating topics replaces the full set — include every topic the webhook should be subscribed to, not just new ones.
  • token (the signing secret) is read-only and returned on every read. To rotate it, delete the webhook and create a new one.
  • To pause deliveries without deleting the webhook, set "active": false.
Need help? Contact your Campfire support team and we’re happy to help you build your best Campfire workflow!