Skip to content
Webhooks SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

Google Pub/Sub

Verify Pub/Sub push deliveries — and Gmail push, Play RTDN, and Workspace Events with them — envelope unwrapped for you.

One provider covers everything Google delivers through Pub/Sub push — Gmail push, Google Play RTDN, Workspace Events, and your own topics. Configure the push subscription’s audience and its invoker service account:

import { createWebhookHandler } from 'webhooks-sdk'
import { googlePubSub } from 'webhooks-sdk/google-pubsub'

const handler = createWebhookHandler({
  provider: googlePubSub({
    audience: 'https://example.com/webhooks/pubsub',
    serviceAccountEmail: '[email protected]',
  }),
  on: {
    message: async (event) => {
      const notification = event.payload.message.json()
      await handle(notification)
    },
  },
})

export const POST = handler.fetch

Each delivery carries an OIDC JWT verified against Google’s rotating keys — issuer, audience, expiry, and service account all checked.

Options

Option Type Default
audience string | string[] — (required) The aud claim configured on the push subscription.
serviceAccountEmail string | string[] — (required) The invoker service account(s) allowed to call you.
keys Jwk[] | KeySet Google’s JWKS, cached Pin keys, or plug in your own cache.
fetch typeof fetch global fetch Override for proxies or tests.
tolerance number 300 Clock skew allowed on exp/iat, in seconds.
eventType string | ((payload) => string | undefined) 'message' Derive the event name — a string names a message attribute.

The envelope is unwrapped for you

Pub/Sub wraps every message in a push envelope with base64-encoded data. The provider unwraps it: event.payload.message carries the decoded bytes with text() and json() accessors, plus messageId, publishTime, attributes, and orderingKey. A Gmail or Play event reaches your handler as an event, not as an envelope.

  • event.id — the Pub/Sub messageId, stable across redelivery, so idempotency works out of the box.
  • event.timestamp — the message’s publishTime.
  • event.type — 'message' by default. Route on an attribute instead with eventType: 'type', or derive: eventType: (p) => p.message.attributes.kind.

No handshake, but a deadline

Pub/Sub sends no setup challenge — it just expects a 2xx before the subscription’s ack deadline, and redelivers otherwise. Keep handlers fast; if the work is slow, enqueue it and return.

Key caching

The provider fetches Google’s JWKS once and caches it — honoring Cache-Control, refreshing when an unknown key id arrives (rate-limited), and falling back to the last good keys if a refresh fails. When Google is unreachable and nothing is cached, verification fails with key_unavailable (a 500) — deliberately not invalid_signature, because it’s an outage, not an attack.

Pass keys to pin JWKs (great for tests) or to supply your own KeySet backed by shared storage.

Standalone & testing

import {
  verifyGooglePubSubWebhook,  // (raw, options) — throws on failure
  parseGooglePubSubWebhook,   // (raw, { eventType? }) — the envelope
  signGooglePubSubWebhook,    // (privateKey, options) — the Authorization header, for tests
} from 'webhooks-sdk/google-pubsub'

Prefer the provider over verifyGooglePubSubWebhook in long-running processes — the standalone function builds a fresh key set (and JWKS fetch) per call unless you pass pinned keys. See Testing for generating a test key pair.

Last updated on September 1, 2026

Was this page helpful?