Quickbooky

Accounting News

Webhooks & Integrations

QuickBooks Online webhooks move to the CloudEvents format

QuickBooks Online app developers report webhook deliveries shifting to CloudEvents, and older handlers stop parsing until they accept both formats.

COMMUNITY ISSUESQUICKBOOKY

App developers who build on the QuickBooks Online API are working through a format change. Webhook notifications, the push messages that tell an app when an invoice or payment changes, are moving to the CloudEvents standard. We reviewed the accepted migration guidance in the developer community, and the short version is this: handlers written for the old payload need a structured move, not a patch.

What changed in the webhook payload?

A legacy delivery arrives as plain JSON with an eventNotifications array in the body. Each entry identifies the company and lists the entities that changed, under the ordinary application/json content type.

CloudEvents is an open specification, stewarded by the Cloud Native Computing Foundation, that wraps event data in a standard envelope. Intuit adopted it for QuickBooks Online webhooks. The business data itself does not change; it moves inside that envelope.

The specification allows two transports, and you should recognize both. In structured mode, the envelope travels in the body under the content type application/cloudevents+json. In binary mode, the body holds only the data, and envelope fields ride in HTTP headers prefixed with ce-, such as ce-specversion, ce-type, ce-id, and ce-time.

The symptoms teams report

An endpoint can acknowledge every delivery and still process nothing. A legacy parser looks for its array, does not find it, and returns success anyway. Nothing crashes, nothing logs, and the app simply stops reacting.

Signature verification breaks for a related reason. The intuit-signature header carries a hash of the raw request bytes, so code that re-serializes the body before checking it sees a mismatch that was never there.

A third sign is quieter: test deliveries arrive intact while production stays silent. That pattern points to a content type branch or a parsing gap, not to connectivity.

Duplicates are a second risk, since retries after a slow response can replay the same event; the ce-id field gives you a key for spotting repeats.

Which state is your handler in?

The accepted guidance sorts your code into one of four states before anyone changes a line. Legacy only, meaning it reads the old array and nothing else, calls for the full migration. CloudEvents only, meaning it already reads the new envelope, calls for verification. Dual format calls for a close look at detection and normalization. No handler at all is out of scope: that is a new build rather than a migration.

The point of sorting first is that verification asks different questions than migration does.

The fix that worked

  1. Update the SDK first. Official QuickBooks Online SDKs for .NET, Java, Node.js, PHP, and Python ship releases that understand the CloudEvents envelope. Do the parsing work against an updated SDK.
  2. Branch on the content type. Treat application/cloudevents+json as the new format and plain application/json as the legacy one, then normalize both into a single internal shape. Downstream logic should never see the seam.
  3. Keep both paths live during the transition. Log which format each delivery used, and retire the legacy branch only when the new shape is all that arrives.
  4. Verify the signature before parsing. Compute the hash over the raw body with the verifier token from your app settings, and compare it against the intuit-signature header. Reject at the edge of the endpoint.
  5. Add a freshness check. The ce-time header gives you an event timestamp, and webhook deliveries can arrive late or more than once. Drop anything outside a window you choose.

A short rollout checklist

Before you call the migration done, confirm the following:

  • The SDK release in use understands the new envelope.
  • Both content types parse to the same internal shape.
  • Signatures are computed over raw bytes, before any re-serialization.
  • A freshness window based on ce-time is set, and rejects are logged.
  • The endpoint returns a success status quickly and processes events off the request path.
  • Unrecognized formats are logged rather than dropped in silence.

The teams that finished cleanly kept both parsers live until the new shape was the only one arriving. That overlap is where mistakes surface, so treat it as part of the migration rather than a delay.

← Back to Community Issues