ComposioHQ/composio · error · ComposioWebhookSignatureVerificationError

No valid v1 signature found in the webhook-signature header.

Error message

No valid v1 signature found in the webhook-signature header. Expected format: 'v1,base64EncodedSignature'

What it means

The 'webhook-signature' header must contain at least one signature in the versioned format 'v1,<base64HMAC>'. The parser splits entries and keeps those whose version prefix is 'v1' with a non-empty value; if none qualify, this ComposioWebhookSignatureVerificationError is thrown. This catches malformed or future-versioned signature headers before any HMAC comparison.

Source

Thrown at ts/packages/core/src/models/Triggers.ts:1270

    if (webhookTimestamp.length === 0) {
      throw new ComposioWebhookSignatureVerificationError(
        "No webhook timestamp was provided. Please pass the value of the 'webhook-timestamp' header."
      );
    }

    // Parse signature - may have multiple signatures prefixed with version (e.g., "v1,base64sig")
    const signatures = signature.split(' ');
    const v1Signatures: string[] = [];

    for (const sig of signatures) {
      const [version, value] = sig.split(',');
      if (version === 'v1' && value) {
        v1Signatures.push(value);
      }
    }

    if (v1Signatures.length === 0) {
      throw new ComposioWebhookSignatureVerificationError(
        'No valid v1 signature found in the webhook-signature header. ' +
          "Expected format: 'v1,base64EncodedSignature'"
      );
    }

    // Compute expected signature: HMAC-SHA256(msgId.timestamp.payload, secret) -> base64
    const toSign = `${webhookId}.${webhookTimestamp}.${payload}`;
    const expectedSignature = await hmacSha256Base64(secret, toSign);

    // Check if any of the provided signatures match
    let isValid = false;
    for (const providedSignature of v1Signatures) {
      if (timingSafeEqual(providedSignature, expectedSignature)) {
        isValid = true;
        break;
      }
    }

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Send/forward the header verbatim from Composio: 'v1,' + base64(HMAC-SHA256(msgId.timestamp.payload, secret))
  2. If computing signatures yourself (tests), prefix the base64 HMAC with 'v1,'
  3. Do not parse/reformat the header before passing it to the SDK
  4. Check for SDK/backend version drift if Composio now sends a different version prefix

Example fix

// before (test helper)
const sig = crypto.createHmac('sha256', secret).update(msg).digest('base64');
// after
const sig = 'v1,' + crypto.createHmac('sha256', secret).update(`${msgId}.${ts}.${payload}`).digest('base64');
Defensive patterns

Strategy: validation

Validate before calling

const sig = String(req.headers['webhook-signature'] ?? '');
const hasV1 = sig.split(' ').some(p => { const [v, val] = p.split(','); return v === 'v1' && !!val; });
if (!hasV1) return res.status(400).send('Bad signature header');

Type guard

const hasV1Signature = (sigHeader: unknown): sigHeader is string =>
  typeof sigHeader === 'string' &&
  sigHeader.split(' ').some(part => { const [v, val] = part.split(','); return v === 'v1' && !!val; });

Try / catch

try { verifyWebhookSignature(...); } catch (e) { if (e instanceof ComposioWebhookSignatureVerificationError) return res.status(400).end(); throw e; }

Prevention

When it happens

Trigger: Passing a signature header that is raw base64 without the 'v1,' prefix, an empty string, a wrong scheme like 'sha256=...', or only unsupported version prefixes (e.g. 'v2,...').

Common situations: Copy-pasting verification code from Svix/Stripe-style schemes (sha256=), hand-crafting test signatures, or a provider version change that alters the signature header format.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/038c15d02fb761f4. Report an issue: GitHub.