Skip to main content
All posts
Automation7 min read

How to Send Vercel Production Deployment Alerts to Your Team's Phones

Verify Vercel's signed deployment.promoted webhook, keep deployment details out of the alert, and send one fixed Ping to a shared release Room.

Two PingRoom avatars watch one verified deployment token enter a warm production alcove while Pingo stands beside the gate.

To send one Vercel production deployment alert to a shared group of phones, subscribe a Vercel account webhook to Deployment Promoted, verify its signature in a small relay, and forward fixed copy to a PingRoom incoming webhook. Use Vercel's delivery ID as the retry identity. Keep commit messages, authors, URLs, logs, and deployment metadata in Vercel.

This is a webhook recipe, not a native Vercel integration. It requires a Vercel Pro or Enterprise team for account webhooks and PingRoom Pro for the destination Room's incoming webhook.

Check Vercel's own notifications first

Vercel already offers configurable dashboard and email notifications on all plans. Its notification guide also documents opt-in push notifications on desktop and mobile web. Those preferences belong to each person; one team member cannot configure another member's channels.

Start with the smaller path:

NeedBetter starting point
One account holder needs Vercel updatesVercel's own notification settings
A team already follows deployment status in SlackVercel's Slack integration
One selected production promotion should reach a bounded shared groupVercel account webhook → relay → PingRoom Room
People need logs, checks, aliases, commit details, or rollback controlsOpen the deployment in Vercel
A release needs paging, escalation, or an immutable operational recordKeep the established release or incident system

Do not forward every preview build. This guide uses Deployment Promoted because Vercel defines that event as a production deployment beginning to serve production traffic. It is a more useful human signal than “a build exists.”

Create the destination Room

Create or choose a private PingRoom Room for the people who need this production update. The Room owner needs PingRoom Pro for incoming webhooks.

Open Settings → Connections → From another app, add a webhook named Vercel production, choose Custom message, and copy the full URL. The incoming-webhook setup guide explains the Room-side steps, while the webhook reference lists request limits, idempotency behavior, and error responses.

Store the URL as PINGROOM_WEBHOOK_URL in protected server configuration. It is a credential. Do not put it in a Vercel environment exposed to the browser, source control, screenshots, or logs.

The relay also needs:

  • VERCEL_WEBHOOK_SECRET: the one-time secret Vercel shows after the account webhook is created;
  • VERCEL_PROJECT_ID: the selected project's prj_… identifier;
  • PINGROOM_PROJECT_LABEL: a short approved label for the lock screen, such as Website.

Verify the raw body before parsing

Vercel's webhook reference says x-vercel-signature is an HMAC-SHA1 digest of the raw request body using the webhook secret. Read the exact bytes once, calculate the expected digest, and compare it in constant time before parsing JSON.

The following Web-standard handler works in a Node runtime. Adapt only the route wrapper for your framework.

import { createHash, createHmac, timingSafeEqual } from "node:crypto";

const MAX_BODY_BYTES = 256_000;

function reply(status, body = null) {
  if (body === null) return new Response(null, { status });
  return Response.json(body, { status });
}

function signaturesMatch(received, expected) {
  if (!/^[a-f0-9]{40}$/i.test(received ?? "")) return false;
  const actual = Buffer.from(received, "hex");
  const wanted = Buffer.from(expected, "hex");
  return actual.length === wanted.length && timingSafeEqual(actual, wanted);
}

export async function handleVercelPromotion(request, env = process.env) {
  if (request.method !== "POST")
    return reply(405, { error: "method_not_allowed" });

  const {
    VERCEL_WEBHOOK_SECRET,
    VERCEL_PROJECT_ID,
    PINGROOM_WEBHOOK_URL,
    PINGROOM_PROJECT_LABEL,
  } = env;

  if (
    !VERCEL_WEBHOOK_SECRET ||
    !VERCEL_PROJECT_ID?.startsWith("prj_") ||
    !PINGROOM_WEBHOOK_URL?.startsWith("https://") ||
    !PINGROOM_PROJECT_LABEL ||
    PINGROOM_PROJECT_LABEL.length > 50
  ) {
    return reply(500, { error: "server_configuration_error" });
  }

  const declaredLength = Number(request.headers.get("content-length") ?? 0);
  if (declaredLength > MAX_BODY_BYTES)
    return reply(413, { error: "body_too_large" });

  const rawBody = await request.text();
  if (Buffer.byteLength(rawBody) > MAX_BODY_BYTES) {
    return reply(413, { error: "body_too_large" });
  }

  const expected = createHmac("sha1", VERCEL_WEBHOOK_SECRET)
    .update(rawBody)
    .digest("hex");
  if (!signaturesMatch(request.headers.get("x-vercel-signature"), expected)) {
    return reply(401, { error: "invalid_signature" });
  }

  let event;
  try {
    event = JSON.parse(rawBody);
  } catch {
    return reply(400, { error: "invalid_json" });
  }

  if (event.type !== "deployment.promoted") return reply(204);

  const projectId = event.payload?.project?.id;
  const deploymentId = event.payload?.deployment?.id;
  const deliveryId = event.id;
  if (!projectId || !deploymentId || !deliveryId) {
    return reply(400, { error: "missing_event_identity" });
  }
  if (projectId !== VERCEL_PROJECT_ID) return reply(204);

  const eventKey = `vercel-promoted-${createHash("sha256")
    .update(String(deliveryId))
    .digest("hex")
    .slice(0, 32)}`;
  const payload = {
    title: "Vercel production deployed",
    message: `${PINGROOM_PROJECT_LABEL} is serving production. Check Vercel before acting.`,
  };

  try {
    const response = await fetch(PINGROOM_WEBHOOK_URL, {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        "Idempotency-Key": eventKey,
      },
      body: JSON.stringify(payload),
      signal: AbortSignal.timeout(8000),
    });

    if (!response.ok) return reply(502, { error: "pingroom_rejected" });
    const accepted = await response.json().catch(() => null);
    if (accepted?.success !== true) {
      return reply(502, { error: "pingroom_invalid_response" });
    }
    return reply(200, { ok: true });
  } catch {
    return reply(502, { error: "pingroom_unavailable" });
  }
}

The handler forwards no field from Vercel's payload. PINGROOM_PROJECT_LABEL is a human-reviewed server setting, not source metadata. If even the project label is sensitive, use a generic value such as Selected project.

Vercel's event ID becomes an opaque, bounded PingRoom idempotency key. If Vercel retries the same delivery after losing a response, PingRoom can return the original accepted result instead of creating another Ping.

Add the Vercel account webhook

Vercel's account-webhook setup is available to Pro and Enterprise teams:

  1. Select the team scope, then open Settings → Webhooks.
  2. Choose Deployment Promoted as the only event for this endpoint.
  3. Scope the webhook to the intended project instead of every team project.
  4. Enter the public HTTPS URL for your relay.
  5. Create the webhook and save its secret immediately as VERCEL_WEBHOOK_SECRET; Vercel does not show it again.
  6. Copy the project's ID from its settings into VERCEL_PROJECT_ID.

The relay checks the project again even though Vercel scopes the webhook. That second boundary keeps a later dashboard change from silently broadening the destination.

deployment.promoted means Vercel has started serving that production deployment. It does not prove that every external dependency is healthy, every cache is warm, every client can reach the site, or every phone displayed the Ping. Keep Vercel as the deployment record and keep any required release verification in the release process.

Give retries one safe outcome

Vercel retries non-2xx webhook deliveries with exponential backoff for up to 24 hours. The relay therefore returns a non-2xx response when PingRoom rejects or cannot accept the handoff.

Relay outcomeHTTP resultWhat Vercel does
Valid promotion accepted by PingRoom200Delivery is complete
Valid but unrelated event or project204Delivery is complete; no Ping is created
Invalid signature or malformed event400/401Delivery remains visibly unsuccessful
PingRoom rejects or times out502Vercel can retry

If configuration stays broken, disable the Vercel webhook or repair the relay before the retry window ends. Do not let a retired Room or expired PingRoom Pro plan produce a day of pointless attempts.

Two PingRoom avatars compare one accepted deployment token with an identical retry stopped outside the verification gate while Pingo watches beside a closed ledger.

These AI-generated editorial illustrations feature approved PingRoom avatars and Pingo the Pigeon, PingRoom's fictional Chief Marketing Officer. They are not Vercel or PingRoom screenshots and do not depict customers.

Pingo the Pigeon is giving the stopped duplicate his usual look. He cannot access Vercel, inspect a private deployment, enter a Room, hold credentials, or send a Ping. The team chooses the project, event, destination, lock-screen copy, and retry policy.

Test the boundary before a real release

Use locally signed fixtures first, then a harmless production promotion for a non-sensitive test project:

  1. Sign a deployment.promoted fixture and confirm one fixed Ping is accepted.
  2. Replay the exact event ID and confirm the Room still has one accepted Ping for that delivery.
  3. Change the project ID and confirm the relay returns 204 without calling PingRoom.
  4. Alter the body after signing and confirm the relay returns 401.
  5. Disable the PingRoom webhook and confirm the relay returns non-2xx so the failure stays visible.
  6. Check the Ping on a locked test phone. Its title and body should reveal no source, commit, author, URL, alias, team, region, or deployment metadata.

For this article, we imported the exact handler above and ran it against locally signed Vercel-style fixtures plus a stateful mock of PingRoom's documented idempotency contract. No live Vercel team, deployment, PingRoom Room, phone, or network request was used.

If one Vercel production promotion deserves a shared signal, create the destination Room and webhook, deploy the relay, and run the six checks with non-sensitive data. Use the broader CI/CD guide to decide which other release states genuinely need attention.

Vercel is a trademark of Vercel Inc. PingRoom is not affiliated with or endorsed by Vercel.

PingRoom

Short-form communication.

View all posts