Skip to main content
Connect supports two categories of webhooks:

Connection webhooks

When you create a Connect token, you can pass a webhook_uri. Pipedream sends a POST request to this URL when the end user successfully connects an account, or if an error occurs during the flow.

Events

  • CONNECTION_SUCCESS — the user successfully connected their account
  • CONNECTION_ERROR — an error occurred during the connection flow

Payload

Successful connection

User credentials are not included in the webhook payload. Use the Retrieve Account endpoint with the account.id from the payload to fetch credentials.
{
  "event": "CONNECTION_SUCCESS",
  "connect_token": "ctok_xxxxxxx",
  "environment": "production",
  "connect_session_id": 1.4058728083872854e+38,
  "account": {
    "id": "apn_xxxxxxx",
    "name": "jane@example.com",
    "external_id": "user-abc-123",
    "healthy": true,
    "dead": null,
    "app": {
      "id": "oa_xxxxxxx",
      "name_slug": "google_sheets",
      "name": "Google Sheets",
      "auth_type": "oauth",
      "description": "Use Google Sheets to create and edit online spreadsheets. Get insights together with secure sharing in real-time and from any device.",
      "img_src": "https://assets.pipedream.net/s.v0/app_168hvn/logo/orig",
      "custom_fields_json": "[]",
      "categories": [
        "Productivity"
      ],
      "featured_weight": 1000000098,
      "connect": {
        "allowed_domains": [
          "www.googleapis.com",
          "sheets.googleapis.com"
        ],
        "base_proxy_target_url": "https://sheets.googleapis.com",
        "proxy_enabled": true
      }
    },
    "created_at": "2026-03-25T17:49:15.000Z",
    "updated_at": "2026-03-25T17:49:15.000Z"
  }
}

Error

{
  "event": "CONNECTION_ERROR",
  "connect_token": "ctok_xxxxxxx",
  "environment": "production",
  "connect_session_id": 1.4058727315359677e+38,
  "error": "Test request failed. Please verify your credentials and try again."
}

Trigger webhooks

When you deploy a trigger with a webhook_url, Pipedream delivers that trigger’s emitted events to your URL via a POST request.

Setting a webhook URL

You can define a webhook destination on an individual trigger, or a project. If both are set, the trigger-level URL takes precedence.

Project-level

Set a default webhook URL for all triggers in a project. You can configure separate URLs for each environment.
In your project settings, add webhook URLs under the Webhooks section for each environment.When you save a new webhook URL, we’ll display the signing key for you to copy and use to validate signatures. Note that we will never show the signing key again, so make sure to note it down.You can also regenerate the signing key and delete or update the webhook URL at any time.

Trigger-level

Pass webhook_url when you deploy a trigger, or update it later with the Update Trigger Webhooks endpoint. Pipedream returns a signing key in the response.

Webhook signatures

Pipedream signs every trigger webhook delivery using HMAC-SHA256. Each request includes an x-pd-signature header:
t=1616000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  • t — UNIX timestamp (seconds) when the signature was generated
  • v1 — HMAC-SHA256 hex digest of the signed payload
The signed payload is generated from the timestamp, a ., and the raw request body:
{timestamp}.{raw_body}

Validating signatures

Compute the expected signature with your signing key and compare it to the x-pd-signature header value.
import crypto from "crypto";

function verifyWebhookSignature(signingKey, signatureHeader, rawBody) {
  const [tPart, v1Part] = signatureHeader.split(",");
  const timestamp = tPart.split("=")[1];
  const receivedSig = v1Part.split("=")[1];

  const signedPayload = `${timestamp}.${rawBody}`;
  const expectedSig = crypto
    .createHmac("sha256", signingKey)
    .update(signedPayload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(receivedSig, "hex"),
    Buffer.from(expectedSig, "hex")
  );
}

// Express example
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const signingKey = process.env.PD_SIGNING_KEY;
  const signatureHeader = req.headers["x-pd-signature"];
  const rawBody = req.body.toString();

  if (!verifyWebhookSignature(signingKey, signatureHeader, rawBody)) {
    return res.status(401).send("Invalid signature");
  }

  // Process the event
  res.status(200).send("OK");
});
Always use a constant-time comparison function (crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) to prevent timing attacks.

Preventing replay attacks

Check the timestamp in the signature header and reject requests that are too old:
const MAX_AGE_SECONDS = 300; // 5 minutes

const currentTime = Math.floor(Date.now() / 1000);
const signatureAge = currentTime - parseInt(timestamp, 10);

if (signatureAge > MAX_AGE_SECONDS) {
  // Reject the request
}