Skip to main content

How to verify Memberstack webhook signatures using the Admin Package without getting header errors?

Hey all đź‘‹,

[Admin Package][Webhooks]

I'm currently setting up some webhooks and I'm running into an issue with the Admin Package (node.js) verifyWebhookSignature method. No matter how I pass the headers into the method, I keep getting an error: "Please provide the svix-id, svix-timestamp, and svix-signature headers."

Here is a simplified snippet of my code:

const memberstackAdmin = require('@memberstack/admin');
const memberstack = memberstackAdmin.init(process.env.MEMBERSTACK_API_KEY);

const handleMemberUpdate = async (req, res) => {
try {
const isValid = memberstack.verifyWebhookSignature({
headers: req.headers,
secret: process.env.MEMBERSTACK_WEBHOOK_SECRET,
payload: req.body.payload,
tolerance: 300,
});

if (!isValid) { console.log('Webhook verification failed'); return res.status(400).send('Webhook verification failed'); } } catch (error) { console.log('Verification error:', error.message); return res.status(400).send('Webhook verification failed'); } // More stuffs

};

module.exports = { handleMemberUpdate };

Despite having the svix-id, svix-timestamp, and svix-signature in the headers (which I have confirmed are being present through logging), the verification keeps failing. Any insights or suggestions on what might be going wrong? Do the headers need to be formatted/parsed or something?

In case someone ever runs into this again, the memberstack method expects the header keys to be in all caps despite the headers being sent in lowercase... kind of annoying, but oh well.
 
On a similar note, there must be some other discrepancy in how the memberstack.verifyWebhookSignature() method expects the payload or other data because I'm running into a similar persistent error:
The signature did not match expected signature for this payload.
I get the error every time despite confirming 1. that the signing secret is correct and passed as a string, 2. the headers are now present (in all caps) and passed exactly as received, and 3. that the payload is passed as an object exactly as received. Is there some kind of expected formatting required for the payload or other parameters?

Thanks in advance for any help!

5 comments

  • Raquel  Lopez
    Raquel Lopez

    It took me more time to figure it out that what I'm proud of... but eventually I got it...

    You need to send the whole body of the request req.body instead of req.body.payload

    const secret = 'whsec_' // your webhook secret found in the MS webhook dashboard;
    const headers = {
    "SVIX-ID": req.headers['svix-id'],
    "SVIX-SIGNATURE": req.headers['svix-signature'],
    "SVIX-TIMESTAMP": req.headers['svix-timestamp'],
    }

    try { const isValid = memberstack.verifyWebhookSignature({ headers: headers, secret: secret, payload: req.body, tolerance: 300 }); if (!isValid) { console.error('Webhook verification failed'); return res.status(400).send('Webhook verification failed'); } console.log("webhook verification successfully"); return res.status(200).send('Webhook verification successfully'); } catch (error) { console.error('Verification error:', error.message); return res.status(400).send('Webhook verification failed'); }
    0
  • Kohlmann Dean
    Kohlmann Dean OP

    Awesome! It's working as expected now. I see the docs are updated to reflect sending the whole body as well. Thanks for updating that!

    0
  • Nathan Roberton
    Nathan Roberton

    Hi Memberstack team — we’re failing webhook signature verification in Test Mode.

    We’re using the endpoint’s “Signing secret” (whsec_…) exactly as displayed.

    Endpoint URL:
    https://ob8d84q19e.execute-api.us-east-1.amazonaws.com/prod/webhooks/memberstack

    For these attempts, our Lambda logs show:
    [1]
    svix-id : msg_31Dj0YGUGJpL7HgBEkBet9QhU2Z
    svix-timestamp : 1755073383
    svix-signature (v1 from header) begins: Za2UCSHqKdVBSA7G…
    body_sha256 : 375c6195cedbaf3a3487bc9bb9dff048bd66f69945c7bc96828f459395fcd371
    our v1 (computed) begins: xIEYI2dyq/d9+ZHx…
    match : False
    [2]
    svix-id : msg_31DzVs3dwLny4wbS9KHPw3Ah6jP
    svix-timestamp : 1755073461
    svix-signature (v1) begins: TgEzpEjN8POO3BKQ…
    body_sha256 : 952698db47a0adebfe9530c51c831f027e83d937d65b52e823ed8b2e154ef5d7
    our v1 begins : UWZmHuMgTkr2Fzr5…
    match : False

    Secret details:

    • Test Mode endpoint’s “Signing secret” length with prefix: 38 chars (starts with whsec_)
    • After removing whsec_ and base64-decoding we get a 24-byte key (expected for Svix).

    Implementation:
    We follow Svix’s spec:
    v1 = base64( HMAC_SHA256( key=base64decode(secret without "whsec_"),
    msg=f"{svix-timestamp}.{raw-body-bytes}" ) )

    Questions:

    1. Can you confirm the expected algorithm for v1 is exactly as above?
    2. Is there any scenario where Test Mode would sign with a different secret than the one shown on the endpoint UI?
    3. For the two svix-id’s above, could you compute your expected v1 and share the first ~12 chars so we can compare?
    4. If helpful, we can DM the exact raw bodies (we included SHA-256 for reference).

    Thanks!

    0
  • Raquel  Lopez
    Raquel Lopez

    Hello Nathan,

    In their docs, Memberstack has an algorithm using the Node JS library to validate a weebhook signature.
    https://developers.memberstack.com/admin-node-package/verification#basic-verification-example

    Could you verify and confirm you're following the instructions in your source code as per the documentation?

    0
  • Nathan Roberton
    Nathan Roberton

    Thanks, Raquel—this helped us get unstuck!

    0

Please sign in to leave a comment.

Sitemap