Skip to content

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

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

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

Conversation

@dylayed

@dylayed dylayed commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Eventarc Firestore triggers push to https://<service>?__GCP_CloudEventsMode=CE_PUBSUB_BINDING. Normally the request is converted to HTTP binary content mode (ce-* headers) before it reaches the function. When an attribute can't be sent as an HTTP header, it isn't converted: the function receives the raw Pub/Sub push envelope, with 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)

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.

@google-cla

google-cla Bot commented Sep 25, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

…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.
@dylayed
dylayed force-pushed the feat/ce-pubsub-binding branch from 53dd316 to 704c17b Compare September 25, 2026 23:17
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.

2 participants