diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml
index 24943e116..39a62ca6b 100644
--- a/.github/.OwlBot.lock.yaml
+++ b/.github/.OwlBot.lock.yaml
@@ -13,5 +13,5 @@
 # limitations under the License.
 docker:
   image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest
-  digest: sha256:609822e3c09b7a1bd90b99655904609f162cc15acb4704f1edf778284c36f429
-# created: 2024-10-01T19:34:30.797530443Z
+  digest: sha256:0d39e59663287ae929c1d4ccf8ebf7cef9946826c9b86eda7e85d8d752dbb584
+# created: 2024-11-21T22:39:44.342569463Z
diff --git a/.github/release-trigger.yml b/.github/release-trigger.yml
index d4ca94189..339b88a21 100644
--- a/.github/release-trigger.yml
+++ b/.github/release-trigger.yml
@@ -1 +1,2 @@
 enabled: true
+multiScmName: nodejs-pubsub
\ No newline at end of file
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 4892eb2c5..e20759835 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -9,10 +9,10 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        node: [14, 16, 18, 20]
+        node: [14, 16, 18]
     steps:
-      - uses: actions/checkout@v3
-      - uses: actions/setup-node@v3
+      - uses: actions/checkout@v4
+      - uses: actions/setup-node@v4
         with:
           node-version: ${{ matrix.node }}
       - run: node --version
@@ -29,10 +29,10 @@ jobs:
   windows:
     runs-on: windows-latest
     steps:
-      - uses: actions/checkout@v3
-      - uses: actions/setup-node@v3
+      - uses: actions/checkout@v4
+      - uses: actions/setup-node@v4
         with:
-          node-version: 14
+          node-version: 18
       - run: npm install --engine-strict
       - run: npm test
         env:
@@ -40,19 +40,19 @@ jobs:
   lint:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
-      - uses: actions/setup-node@v3
+      - uses: actions/checkout@v4
+      - uses: actions/setup-node@v4
         with:
-          node-version: 14
+          node-version: 18
       - run: npm install
       - run: npm run lint
   docs:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
-      - uses: actions/setup-node@v3
+      - uses: actions/checkout@v4
+      - uses: actions/setup-node@v4
         with:
-          node-version: 14
+          node-version: 18
       - run: npm install
       - run: npm run docs
       - uses: JustinBeckwith/linkinator-action@v1
diff --git a/.kokoro/common.cfg b/.kokoro/common.cfg
index d8038cd99..dabc141b2 100644
--- a/.kokoro/common.cfg
+++ b/.kokoro/common.cfg
@@ -16,7 +16,7 @@ build_file: "nodejs-pubsub/.kokoro/trampoline_v2.sh"
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
     key: "TRAMPOLINE_IMAGE"
-    value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user"
+    value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
 }
 env_vars: {
     key: "TRAMPOLINE_BUILD_FILE"
diff --git a/.kokoro/continuous/node18/common.cfg b/.kokoro/continuous/node18/common.cfg
new file mode 100644
index 000000000..dabc141b2
--- /dev/null
+++ b/.kokoro/continuous/node18/common.cfg
@@ -0,0 +1,24 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+# Build logs will be here
+action {
+  define_artifacts {
+    regex: "**/*sponge_log.xml"
+  }
+}
+
+# Download trampoline resources.
+gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
+
+# Use the trampoline script to run in docker.
+build_file: "nodejs-pubsub/.kokoro/trampoline_v2.sh"
+
+# Configure the docker image for kokoro-trampoline.
+env_vars: {
+    key: "TRAMPOLINE_IMAGE"
+    value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
+}
+env_vars: {
+    key: "TRAMPOLINE_BUILD_FILE"
+    value: "github/nodejs-pubsub/.kokoro/test.sh"
+}
diff --git a/.kokoro/continuous/node18/lint.cfg b/.kokoro/continuous/node18/lint.cfg
new file mode 100644
index 000000000..219d3808d
--- /dev/null
+++ b/.kokoro/continuous/node18/lint.cfg
@@ -0,0 +1,4 @@
+env_vars: {
+    key: "TRAMPOLINE_BUILD_FILE"
+    value: "github/nodejs-pubsub/.kokoro/lint.sh"
+}
diff --git a/.kokoro/continuous/node18/samples-test.cfg b/.kokoro/continuous/node18/samples-test.cfg
new file mode 100644
index 000000000..698013d7c
--- /dev/null
+++ b/.kokoro/continuous/node18/samples-test.cfg
@@ -0,0 +1,12 @@
+# Download resources for system tests (service account key, etc.)
+gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs"
+
+env_vars: {
+    key: "TRAMPOLINE_BUILD_FILE"
+    value: "github/nodejs-pubsub/.kokoro/samples-test.sh"
+}
+
+env_vars: {
+  key: "SECRET_MANAGER_KEYS"
+  value: "long-door-651-kokoro-system-test-service-account"
+}
\ No newline at end of file
diff --git a/.kokoro/continuous/node18/system-test.cfg b/.kokoro/continuous/node18/system-test.cfg
new file mode 100644
index 000000000..bf759bd3f
--- /dev/null
+++ b/.kokoro/continuous/node18/system-test.cfg
@@ -0,0 +1,12 @@
+# Download resources for system tests (service account key, etc.)
+gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs"
+
+env_vars: {
+    key: "TRAMPOLINE_BUILD_FILE"
+    value: "github/nodejs-pubsub/.kokoro/system-test.sh"
+}
+
+env_vars: {
+  key: "SECRET_MANAGER_KEYS"
+  value: "long-door-651-kokoro-system-test-service-account"
+}
\ No newline at end of file
diff --git a/.kokoro/continuous/node18/test.cfg b/.kokoro/continuous/node18/test.cfg
new file mode 100644
index 000000000..e69de29bb
diff --git a/.kokoro/presubmit/node18/common.cfg b/.kokoro/presubmit/node18/common.cfg
new file mode 100644
index 000000000..dabc141b2
--- /dev/null
+++ b/.kokoro/presubmit/node18/common.cfg
@@ -0,0 +1,24 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+# Build logs will be here
+action {
+  define_artifacts {
+    regex: "**/*sponge_log.xml"
+  }
+}
+
+# Download trampoline resources.
+gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
+
+# Use the trampoline script to run in docker.
+build_file: "nodejs-pubsub/.kokoro/trampoline_v2.sh"
+
+# Configure the docker image for kokoro-trampoline.
+env_vars: {
+    key: "TRAMPOLINE_IMAGE"
+    value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
+}
+env_vars: {
+    key: "TRAMPOLINE_BUILD_FILE"
+    value: "github/nodejs-pubsub/.kokoro/test.sh"
+}
diff --git a/.kokoro/presubmit/node18/samples-test.cfg b/.kokoro/presubmit/node18/samples-test.cfg
new file mode 100644
index 000000000..698013d7c
--- /dev/null
+++ b/.kokoro/presubmit/node18/samples-test.cfg
@@ -0,0 +1,12 @@
+# Download resources for system tests (service account key, etc.)
+gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs"
+
+env_vars: {
+    key: "TRAMPOLINE_BUILD_FILE"
+    value: "github/nodejs-pubsub/.kokoro/samples-test.sh"
+}
+
+env_vars: {
+  key: "SECRET_MANAGER_KEYS"
+  value: "long-door-651-kokoro-system-test-service-account"
+}
\ No newline at end of file
diff --git a/.kokoro/presubmit/node18/system-test.cfg b/.kokoro/presubmit/node18/system-test.cfg
new file mode 100644
index 000000000..bf759bd3f
--- /dev/null
+++ b/.kokoro/presubmit/node18/system-test.cfg
@@ -0,0 +1,12 @@
+# Download resources for system tests (service account key, etc.)
+gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs"
+
+env_vars: {
+    key: "TRAMPOLINE_BUILD_FILE"
+    value: "github/nodejs-pubsub/.kokoro/system-test.sh"
+}
+
+env_vars: {
+  key: "SECRET_MANAGER_KEYS"
+  value: "long-door-651-kokoro-system-test-service-account"
+}
\ No newline at end of file
diff --git a/.kokoro/presubmit/node18/test.cfg b/.kokoro/presubmit/node18/test.cfg
new file mode 100644
index 000000000..e69de29bb
diff --git a/.kokoro/release/docs-devsite.cfg b/.kokoro/release/docs-devsite.cfg
index b5638214a..b1ebf97bc 100644
--- a/.kokoro/release/docs-devsite.cfg
+++ b/.kokoro/release/docs-devsite.cfg
@@ -11,7 +11,7 @@ before_action {
 # doc publications use a Python image.
 env_vars: {
     key: "TRAMPOLINE_IMAGE"
-    value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user"
+    value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
 }
 
 # Download trampoline resources.
diff --git a/.kokoro/release/docs.cfg b/.kokoro/release/docs.cfg
index 508a0efda..ef7147c33 100644
--- a/.kokoro/release/docs.cfg
+++ b/.kokoro/release/docs.cfg
@@ -11,7 +11,7 @@ before_action {
 # doc publications use a Python image.
 env_vars: {
     key: "TRAMPOLINE_IMAGE"
-    value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user"
+    value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
 }
 
 # Download trampoline resources.
diff --git a/.kokoro/release/docs.sh b/.kokoro/release/docs.sh
index 1d8f3f490..e9079a605 100755
--- a/.kokoro/release/docs.sh
+++ b/.kokoro/release/docs.sh
@@ -16,7 +16,7 @@
 
 set -eo pipefail
 
-# build jsdocs (Python is installed on the Node 10 docker image).
+# build jsdocs (Python is installed on the Node 18 docker image).
 if [[ -z "$CREDENTIALS" ]]; then
   # if CREDENTIALS are explicitly set, assume we're testing locally
   # and don't set NPM_CONFIG_PREFIX.
diff --git a/.kokoro/release/publish.cfg b/.kokoro/release/publish.cfg
index 91fb161ec..ae6223c88 100644
--- a/.kokoro/release/publish.cfg
+++ b/.kokoro/release/publish.cfg
@@ -30,7 +30,7 @@ build_file: "nodejs-pubsub/.kokoro/trampoline_v2.sh"
 # Configure the docker image for kokoro-trampoline.
 env_vars: {
     key: "TRAMPOLINE_IMAGE"
-    value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user"
+    value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
 }
 
 env_vars: {
diff --git a/.kokoro/samples-test.sh b/.kokoro/samples-test.sh
index 8c5d108cb..528775394 100755
--- a/.kokoro/samples-test.sh
+++ b/.kokoro/samples-test.sh
@@ -16,7 +16,9 @@
 
 set -eo pipefail
 
-export NPM_CONFIG_PREFIX=${HOME}/.npm-global
+# Ensure the npm global directory is writable, otherwise rebuild `npm`
+mkdir -p $NPM_CONFIG_PREFIX
+npm config -g ls || npm i -g npm@`npm --version`
 
 # Setup service account credentials.
 export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secret_manager/long-door-651-kokoro-system-test-service-account
@@ -56,7 +58,7 @@ fi
 
 # codecov combines coverage across integration and unit tests. Include
 # the logic below for any environment you wish to collect coverage for:
-COVERAGE_NODE=14
+COVERAGE_NODE=18
 if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then
   NYC_BIN=./node_modules/nyc/bin/nyc.js
   if [ -f "$NYC_BIN" ]; then
diff --git a/.kokoro/system-test.sh b/.kokoro/system-test.sh
index 0b3043d26..a90d5cfec 100755
--- a/.kokoro/system-test.sh
+++ b/.kokoro/system-test.sh
@@ -49,7 +49,7 @@ npm run system-test
 
 # codecov combines coverage across integration and unit tests. Include
 # the logic below for any environment you wish to collect coverage for:
-COVERAGE_NODE=14
+COVERAGE_NODE=18
 if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then
   NYC_BIN=./node_modules/nyc/bin/nyc.js
   if [ -f "$NYC_BIN" ]; then
diff --git a/.kokoro/test.bat b/.kokoro/test.bat
index 0bb124052..caf825656 100644
--- a/.kokoro/test.bat
+++ b/.kokoro/test.bat
@@ -21,7 +21,7 @@ cd ..
 @rem we upgrade Node.js in the image:
 SET PATH=%PATH%;/cygdrive/c/Program Files/nodejs/npm
 
-call nvm use v14.17.3
+call nvm use 18
 call which node
 
 call npm install || goto :error
diff --git a/.kokoro/test.sh b/.kokoro/test.sh
index 862d478d3..0d9f6392a 100755
--- a/.kokoro/test.sh
+++ b/.kokoro/test.sh
@@ -39,7 +39,7 @@ npm test
 
 # codecov combines coverage across integration and unit tests. Include
 # the logic below for any environment you wish to collect coverage for:
-COVERAGE_NODE=14
+COVERAGE_NODE=18
 if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then
   NYC_BIN=./node_modules/nyc/bin/nyc.js
   if [ -f "$NYC_BIN" ]; then
diff --git a/.kokoro/trampoline_v2.sh b/.kokoro/trampoline_v2.sh
index 4d0311212..5d6cfcca5 100755
--- a/.kokoro/trampoline_v2.sh
+++ b/.kokoro/trampoline_v2.sh
@@ -44,7 +44,7 @@
 # the project root.
 #
 # Here is an example for running this script.
-#   TRAMPOLINE_IMAGE=gcr.io/cloud-devrel-kokoro-resources/node:10-user \
+#   TRAMPOLINE_IMAGE=gcr.io/cloud-devrel-kokoro-resources/node:18-user \
 #     TRAMPOLINE_BUILD_FILE=.kokoro/system-test.sh \
 #     .kokoro/trampoline_v2.sh