Verifying Webhooks

To ensure the security of your webhook endpoints, CyberBlog signs all webhook requests using HMAC SHA-256. You should always verify these signatures before processing webhook data to prevent spoofing attacks.

How It Works

  1. Each webhook has a unique signing secret (starts with whsec_)
  2. We include three headers with each request:
    • svix-id: A unique identifier for the request (format: msg_<32_random_hex_chars>)
    • svix-timestamp: When the webhook was sent (Unix timestamp in seconds)
    • svix-signature: The HMAC signature of the payload (format: v1,<base64_signature>)
⚠️

Never share or commit your webhook signing secret. Use environment variables to store it securely.

Implementation Example

Here’s how to verify webhooks using Next.js and the svix library:

import { NextResponse } from 'next/server'
import { headers } from 'next/headers'
import { Webhook } from 'svix'
 
// Your webhook secret (starts with whsec_)
const secret = process.env.CYBERBLOG_WEBHOOK_SECRET    // whsec_1234567890abcdef....
 
export async function POST(request: Request) {
  // Get the raw request body
  const payload = await request.text()
  const headersList = headers()
 
  // Get the required Svix headers
  const svixId = headersList.get('svix-id')
  const svixTimestamp = headersList.get('svix-timestamp')
  const svixSignature = headersList.get('svix-signature')
 
  // Verify all headers are present
  if (!svixId || !svixTimestamp || !svixSignature) {
    return NextResponse.json({ message: 'Missing headers' }, { status: 400 })
  }
 
  try {
    // Verify the webhook signature
    const wh = new Webhook(secret)
    const event = wh.verify(payload, {
      "svix-id": svixId,
      "svix-timestamp": svixTimestamp,
      "svix-signature": svixSignature,
    })
 
    // Handle the verified webhook data here
    // For example, you might want to save the blog post to your database
    console.log('Webhook verified:', event)
    
    return NextResponse.json({ message: 'Success' }, { status: 200 })
  } catch (err) {
    console.error('Verification failed:', err)
    return NextResponse.json({ message: 'Invalid webhook' }, { status: 400 })
  }
}

Other Languages / Frameworks

If you’re not using Next.js, you’ll need to implement the HMAC-SHA256 verification yourself. Here’s how the verification process works:

Want to implement this in your language? Copy this text and send it to your favorite AI assistant:

I need to implement webhook signature verification similar to this Next.js example, but in [YOUR_LANGUAGE/FRAMEWORK]. The webhook uses HMAC SHA-256 for signing and includes these headers:
- svix-id: Unique request identifier
- svix-timestamp: When webhook was sent
- svix-signature: The HMAC signature (format: v1,<base64_signature>)

The signature is created by:
1. Concatenating: svix-id + "." + svix-timestamp + "." + raw_payload
2. Creating HMAC SHA-256 using base64-decoded webhook secret
3. Base64 encoding the result

Please provide a complete implementation in [YOUR_LANGUAGE/FRAMEWORK] that:
1. Extracts the required headers
2. Verifies the signature
3. Returns 400 for invalid/missing signatures
4. Returns 200 for valid webhooks
5. Includes proper error handling

Here is an example of how to do this in Next.js: [PUT_THE_CODE_ABOVE_HERE]

Understanding the Process

  1. Headers: Each webhook request includes three critical headers:

    • svix-id: A unique identifier for each request (format: msg_<32_random_hex_chars>)
    • svix-timestamp: Unix timestamp in seconds when the webhook was sent
    • svix-signature: The signature in format v1,<base64_signature>
  2. Payload Structure:

{
  event: "post.published",
  data: {
    id: string,
    title: string,
    description: string,
    content: string,
    slug: string,
    publishedAt: Date,
    websiteUrl: string
  }
}
  1. Verification Steps:
    • Extract the required headers
    • Verify the timestamp is within tolerance (usually 5 minutes)
    • Create the signed content by concatenating: svix-id.svix-timestamp.payload
    • Base64 decode your webhook secret (remove the whsec_ prefix and decode the remainder)
    • Generate HMAC SHA-256 using the decoded secret
    • Base64 encode the result
    • Compare with the provided signature using constant-time comparison

Common Pitfalls

  • Not using constant-time string comparison
  • Incorrect concatenation order of the signed content
  • Not verifying the timestamp to prevent replay attacks
  • Incorrect handling of the base64-decoded secret
  • Not using HTTPS for webhook endpoints

Implementation Tips

  1. Security:

    • Always use environment variables for webhook secrets
    • Verify signatures before processing any webhook data
    • Use HTTPS for all webhook endpoints
    • Implement proper error handling
  2. Best Practices:

    • Return 400 status for invalid signatures
    • Return 200 status for valid webhooks
    • Log failed verification attempts
    • Consider implementing retry logic

Need help implementing this in your language? Copy the Next.js example above and ask your favorite AI assistant to translate it to your preferred language while following these security guidelines.