Skip to content

feat: support Eventarc CE_PUBSUB_BINDING deliveries for CloudEvent functions - #805

Draft
taeold wants to merge 1 commit into
GoogleCloudPlatform:mainfrom
taeold:feat/ce-pubsub-binding
Draft

taeold wants to merge 1 commit into
GoogleCloudPlatform:mainfrom
taeold:feat/ce-pubsub-binding

Conversation

@taeold

@taeold taeold commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Eventarc Firestore triggers deliver through a Pub/Sub push subscription whose endpoint is https://<service>?__GCP_CloudEventsMode=CE_PUBSUB_BINDING. Before the request reaches the function, it is normally converted to HTTP binary content mode (ce-* headers), and the query parameter is removed, so functions normally never see it. When an attribute can't be sent as an HTTP header, the conversion doesn't happen. The function instead receives the raw Pub/Sub push envelope, with the query parameter still on the URL, and the CloudEvent in binary content mode of the Google Cloud Pub/Sub protocol binding (ce--prefixed attributes, data in message.data).

The known trigger is a Firestore document ID with a trailing space, which puts a trailing space at the end of ce-subject and ce-document (firebase/firebase-functions#1922). FF handles the envelope as a plain Pub/Sub message and delivers a google.cloud.pubsub.topic.v1.messagePublished event with source: //pubsub.googleapis.com/. With firebase-functions 7.4.0 the handler now runs with params: {} and a non-existent snapshot, and the event is acked.

This adds a middleware, for the cloudevent signature type only, that decodes the envelope into the CloudEvent it carries and hands it to the existing structured-mode path. It's an alternative to #786, which spoofs HTTP headers and also matches unprefixed type/source attributes.

Scope

The middleware only applies when all of these hold. Everything else is left exactly as today:

  • the query string has __GCP_CloudEventsMode=CE_PUBSUB_BINDING
  • there are no ce-* HTTP headers (a binary-mode HTTP CloudEvent still takes precedence)
  • the message has ce-specversion, ce-id, ce-source and ce-type attributes
  • it is binary content mode (no content-type: application/cloudevents... attribute)

In testing, only unconverted requests carried the parameter; converted requests arrive at /.

Data is decoded the way the HTTP body parsers in server.ts decode a binary-mode body: application/json is parsed (invalid JSON returns 400, like bodyParser.json), text/plain becomes a string, and anything else, including application/protobuf, stays a Buffer. datacontenttype comes from ce-datacontenttype, falling back to the content-type attribute.

Not included, on purpose: structured content mode, and the event signature type.

Backward compatibility

  • Pub/Sub-source Eventarc triggers use a different mode (CUSTOM_PUBSUB_<topic>) and send their own ce-* headers, so user messages with ce-* attributes on those triggers are unaffected.
  • User-configured push subscriptions don't carry the CE_PUBSUB_BINDING parameter, so they're unaffected.
  • If the parameter ever changes, the middleware stops matching and behavior falls back to what it is today.
  • The only behavior change: a cloudevent function that gets a CE_PUBSUB_BINDING envelope now receives the real event instead of a messagePublished wrapper.

Testing

  • npm test: 120 passing (13 new in test/integration/cloud_event_pubsub_binding.ts); npm run check clean.
  • With the middleware disabled, the 6 decoding tests fail and the 7 "left unchanged" tests still pass.
  • End to end on GCP: a Firestore document.v1.written trigger → a firebase-functions 7.4.0 onDocumentWritten function, deployed with FUNCTION_SIGNATURE_TYPE=cloudevent (as the Firebase CLI does) and this branch packed in with npm pack. Seven document IDs were tested: normal, internal space, leading space, trailing space, %, ", é. All returned 200 and arrived as Firestore events. The trailing-space ID now gets params.docId = "trailing-space " and the correct snapshot, where it previously got empty params and a missing document. The other six are unchanged.

Unrelated issue found while testing: non-ASCII document IDs reach params garbled (é → é), because the header carries raw UTF-8 that Node reads as Latin-1. It's on the normal HTTP path and not touched here.

…nctions

Eventarc delivers CloudEvents to Pub/Sub push endpoints marked with
`?__GCP_CloudEventsMode=CE_PUBSUB_BINDING`. These are normally converted to
HTTP binary content mode before reaching the function, but when an attribute
can't be sent as an HTTP header (for example, a Firestore document ID with a
trailing space in `ce-subject`), the request arrives as the raw Pub/Sub push
envelope with the CloudEvent in binary content mode of the Google Cloud
Pub/Sub protocol binding. FF treated these as plain Pub/Sub messages and
delivered a `messagePublished` event instead.

For the `cloudevent` signature type, decode these envelopes into the
CloudEvent they carry. Only binary content mode is handled, and only when the
query parameter is present and all required `ce-` attributes are set; all
other Pub/Sub push requests are left unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant