Gareth Wilson Gareth Wilson

Guide to Pipedream Webhooks: Features and Best Practices

Published


Pipedream Connect lets your app's users connect their own accounts on other apps and run Pipedream triggers on their behalf. Connect sends webhooks back to your app at two moments: when a user connects an account (or fails to), and whenever a deployed trigger emits an event, such as a new row in a user's spreadsheet or a new issue in their tracker. If you build on Connect, these webhooks are how your app finds out about new accounts and receives trigger events without polling.

This guide covers the two kinds of Connect webhook, how trigger deliveries are signed and verified, the gaps in Pipedream's delivery documentation, and the best practices for running a receiver in production. It is about webhooks Pipedream sends to your app. HTTP triggers that receive requests into a Pipedream workflow are a different feature.

What are Pipedream Connect webhooks?

Connect has two categories of webhook, and they behave differently.

Connection webhooks report the result of a user's account connection. You set the URL as webhook_uri when you create a Connect token, and Pipedream sends a POST with CONNECTION_SUCCESS or CONNECTION_ERROR in the event field. Pipedream's docs say nothing about signing these.

Trigger webhooks deliver the events a deployed trigger emits. You set the URL on the trigger or as a default for the project, and Pipedream signs every delivery with HMAC-SHA256 in the x-pd-signature header. The body is whatever the trigger emits. Pipedream documents no envelope or event-type field for it.

Pipedream Connect webhook features

FeatureConnection webhooksTrigger webhooks
URLwebhook_uri on the Connect tokenTrigger-level webhook_url, or a project default per environment
MethodPOSTPOST
EventsCONNECTION_SUCCESS, CONNECTION_ERRORWhatever the trigger emits
SignatureNot documentedx-pd-signature: t=<unix>,v1=<hex>
SchemeNone documentedHMAC-SHA256 over {t}.{raw body}, hex encoded
SecretNoneA signing key per destination, shown or returned once
Event IDNoneNone documented

Pipedream does not document retries, a response timeout, the status it treats as success, or ordering, for either category.

Common events

Connection webhooks

A successful connection includes the account and the app it belongs to. Credentials are not included: use the Retrieve Account endpoint with account.id to fetch them.

{
  "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"
    },
    "created_at": "2026-03-25T17:49:15.000Z",
    "updated_at": "2026-03-25T17:49:15.000Z"
  }
}

A failed connection carries an error string in place of account:

{
  "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."
}

Note that connect_session_id is printed as a very large floating-point number. If you store it, keep it as a string, because a number that size loses precision.

Trigger webhooks

A trigger webhook's body is the event the trigger component emits, so its shape depends on the trigger. Pipedream doesn't add a documented event field to trigger deliveries. Use the URL you configured, such as a path per trigger, to tell triggers apart.

Setting up Pipedream Connect webhooks

Connection webhooks

Pass webhook_uri when you create a Connect token. Pipedream sends the connection result for that token's flow to the URL.

Trigger webhooks

Set a project-level default URL for each environment, or a URL on each trigger. If both are set, the trigger-level URL wins.

  • Project default, in the UI: in your project settings, add a webhook URL for each environment under Webhooks. Pipedream shows the signing key when you save, and "will never show the signing key again", so copy it straight away.
  • Project default, with the API:
curl -X PUT "https://api.pipedream.com/v1/connect/{project_id}/webhook" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {access_token}" \
  -H "x-pd-environment: production" \
  -d '{ "webhook_url": "https://example.com/webhooks/pipedream" }'
  • Per trigger: pass webhook_url when you deploy a trigger, or change it with Update Trigger Webhooks. The response includes a signing key.

Each destination has its own signing key, and you can regenerate a key at any time. Store each key server-side, for example as PIPEDREAM_SIGNING_KEY.

Securing Pipedream Connect webhooks

For trigger webhooks, parse t and v1 from x-pd-signature, compute an HMAC-SHA256 of {t}.{raw body} with your signing key, hex-encode it, and compare it in constant time with v1.

const crypto = require('crypto');
const express = require('express');

function verifyPipedreamSignature(rawBody, signatureHeader, signingKey) {
  if (typeof signatureHeader !== 'string' || !signingKey) return null;

  const parts = Object.fromEntries(
    signatureHeader.split(',').map((part) => {
      const index = part.indexOf('=');
      return [part.slice(0, index).trim(), part.slice(index + 1).trim()];
    })
  );
  const timestamp = parts.t;
  const signature = parts.v1;
  if (!timestamp || !signature) return null;

  const expected = crypto
    .createHmac('sha256', signingKey)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest();
  const received = Buffer.from(signature, 'hex');
  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {
    return null;
  }
  return Number(timestamp);
}

const app = express();

app.post('/webhooks/pipedream/triggers', express.raw({ type: '*/*' }), (req, res) => {
  const signingKey = process.env.PIPEDREAM_SIGNING_KEY;
  if (!signingKey) return res.sendStatus(500); // fail closed

  const timestamp = verifyPipedreamSignature(req.body, req.headers['x-pd-signature'], signingKey);
  if (timestamp === null) return res.sendStatus(401);

  // Optional: Pipedream's docs suggest rejecting signatures older than 300 seconds.
  const event = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200);
});

The same check in Python:

import hashlib
import hmac


def verify_pipedream_signature(raw_body: bytes, signature_header: str, signing_key: str) -> bool:
    if not signature_header or not signing_key:
        return False
    parts = dict(part.strip().split("=", 1) for part in signature_header.split(",") if "=" in part)
    timestamp, signature = parts.get("t"), parts.get("v1")
    if not timestamp or not signature:
        return False
    signed_payload = f"{timestamp}.".encode() + raw_body
    expected = hmac.new(signing_key.encode("utf-8"), signed_payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected.encode(), signature.encode())

Pipedream's docs show an optional replay check: reject the request if t is more than 300 seconds old. Whether to apply it is up to you. If you use it, allow for retries from anything between Pipedream and your service.

To check your verifier, use the signing key pd_signing_key_guide_test and this exact body:

{"issue":{"id":42,"title":"Example"}}

With t=1761040800, the x-pd-signature header should be:

t=1761040800,v1=563618387b60ff4870bcfe0b55f3b95711ad9593fb8bddeeea7b320ea0b85bd3

Connection webhooks are not documented as signed, so there is nothing to verify. Send them to a separate endpoint and treat them as a prompt to call Retrieve Account, which you authenticate with your own credentials, rather than trusting the body.

Make Pipedream webhooks production-ready. Hookdeck Event Gateway verifies x-pd-signature on every trigger delivery, stores each event durably, and lets you retry and replay what your service missed.

Pipedream Connect webhook limitations and pain points

Connection webhooks have no documented signature

The Problem: Anyone who learns your webhook_uri can post a fake CONNECTION_SUCCESS.

Why It Happens: Pipedream documents a signature only for trigger webhooks. The connection webhook section names no header or secret.

Workarounds:

  • Use a hard-to-guess URL for webhook_uri, served over HTTPS.
  • Never act on the body alone. Fetch the account with Retrieve Account using account.id, and only continue if it exists and belongs to the expected external_id.

How Hookdeck Can Help: A Hookdeck Pipedream source with verification on checks x-pd-signature, so it is for trigger webhooks. Point connection webhooks at a second source with verification off, and you still get storage, retries and replay for them.

Delivery behaviour is not documented

The Problem: You can't tell how long your endpoint has to respond, whether a failed delivery is retried, or whether events arrive in order.

Why It Happens: Pipedream's Connect webhook docs don't cover retries, timeouts, the success status or ordering for either category.

Workarounds:

  • Return a 2xx as soon as the signature checks out, and process the event afterwards.
  • Assume a failed delivery may not come back, and reconcile important state from the Connect API.

How Hookdeck Can Help: Hookdeck accepts each delivery as soon as it arrives and stores it, so Pipedream's attempt succeeds even while your service is down. You then deliver to your service with your own retry policy, and issues alert you when it fails.

Several signing keys

The Problem: Signatures fail for some triggers but not others.

Why It Happens: The project default and each trigger-level URL have their own signing key. A trigger-level URL takes precedence over the project default, so one receiver can see deliveries signed with different keys.

Workarounds:

  • Give each signing key its own endpoint path, so you always know which key to use.
  • Keep a record of which triggers have their own webhook_url.

How Hookdeck Can Help: A Hookdeck source holds one signing key. Create one source per key, and each one verifies its own deliveries before routing them to the same service.

Trigger payloads have no envelope

The Problem: Your handler can't tell which trigger sent an event, or whether it has seen it before.

Why It Happens: Trigger webhook bodies are exactly what each trigger emits. Pipedream documents no event type, trigger ID or event ID added to them.

Workarounds:

  • Use a URL per trigger, for example with the trigger ID in the path or query string.
  • Build a deduplication key from fields in the emitted event that are unique for that app, such as the record's ID.

How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on the fields you choose, and filters can route events by path or by fields in the body.

Best practices

Save the signing key when you see it

The UI shows a project's signing key once. If you lose it, regenerate it and update your receiver.

Verify against the raw body

The signature covers the exact bytes Pipedream sent. Use express.raw() or your framework's raw body before parsing JSON.

Don't trust connection webhooks on their own

Use them as a prompt to fetch the account from the API. Credentials are never in the payload anyway.

Store large IDs as strings

connect_session_id appears as a large floating-point number in Pipedream's examples. Treat it as a string so you don't lose precision.

Acknowledge fast and process asynchronously

Return 200 after verification and queue the work. See why to process webhooks asynchronously.

Make handlers idempotent

Processing the same event twice should have the same result as processing it once. See our guide to webhook idempotency.

Conclusion

Pipedream Connect sends two kinds of webhook. Connection webhooks report CONNECTION_SUCCESS or CONNECTION_ERROR and are not documented as signed, so confirm them through the API. Trigger webhooks carry each trigger's emitted events and an x-pd-signature HMAC over the timestamp and raw body, which you verify with the signing key for that destination.

Hookdeck Event Gateway verifies trigger signatures, stores every delivery before your service sees it, and adds the retries and replay that Pipedream's docs don't describe.

Get started with Hookdeck for free and handle Pipedream webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.