How webhooks work
Language

Webhooks

Set up webhook destinations to receive c/side security alerts via HTTP POST requests with HMAC signature verification.

How webhooks work

Webhooks let you receive c/side notifications as HTTP POST requests to a URL you specify. When a trigger fires, c/side sends a JSON payload to your webhook endpoint containing details about the event.

You can configure webhooks in three formats:

Adding a webhook destination

To add a webhook as a destination in a notification config:

  1. Open the dashboard and navigate to Team Settings > Notifications
  2. Create a new notification config or edit an existing one
  3. Under Send To, click Add destination and select Webhook
  4. In the Configuration panel, fill in:
    • Endpoint URL: the URL where you want to receive webhook notifications (e.g., https://example.com/cside/notify)
    • Secret: a secret key used to sign webhook requests (used for HMAC verification)
    • Format: choose JSON, Slack, or Discord
  5. Click Save or Save & Test

The webhook payload

Every webhook request contains a structured JSON payload with the following fields:

Prop
Type
event
"alert.created"
The event code
sent_at
string
When the event was sent in ISO 8601 format
data
AlertCreatedObject
The data of the event
domain_id
string
The domain ID that the event is for
destination_id
string
The ID of the endpoint destination

Events

Below are the event IDs and their payloads sent with webhooks.

Event IDs

IDNamedata Object
alert.createdAlert CreatedAlert Object

Event objects

HMAC verification

Every webhook request includes an x-cside-signature header containing an HMAC SHA256 hash of the request body, signed with the secret you provided when creating the webhook destination.

You should always verify this signature to confirm the request came from c/side and prevent replay attacks.

How to verify the signature

If you are using JavaScript, use our JavaScript package for built-in verification.

For other languages, compute the HMAC SHA256 hash of the raw request body using your webhook secret, then compare it to the x-cside-signature header value.

async function verifyHmac(body: string, signature: string, secret: string) {
	const encoder = new TextEncoder();
	const encodedBody = encoder.encode(body);

	const key = await crypto.subtle.importKey(
		"raw",
		encoder.encode(secret),
		{ name: "HMAC", hash: "SHA-256" },
		false,
		["sign"]
	);

	const signatureBuffer = await crypto.subtle.sign("HMAC", key, encodedBody);

	const finalSig = Array.from(new Uint8Array(signatureBuffer))
		.map((byte) => byte.toString(16).padStart(2, "0"))
		.join("");

	return signature.toLowerCase() === finalSig;
}
fn verify_hmac(body: &str, signature: &str, secret: &str) -> Result<(), &'static str> {
      let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).map_err(|_| "Invalid secret")?;
      mac.update(body.as_bytes());
      let result = mac.finalize();
      let code_bytes = result.into_bytes();
      let final_sig = hex::encode(code_bytes);

      if signature.to_lowercase() == final_sig {
          Ok(())
      } else {
          Err("Invalid signature")
      }
  }

  // Add to Cargo.toml:
  // [dependencies]
  // hmac = "0.12"
  // sha2 = "0.10"
  // hex = "0.4"
package main

import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/hex"
  "strings"
)

func verifyHmac(body, signature, secret string) bool {
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(body))
	finalSig := hex.EncodeToString(h.Sum(nil))

	return strings.ToLower(signature) == finalSig
}
defmodule HmacVerifier do
	def verify_hmac(body, signature, secret) do
		final_sig =
			:crypto.mac(:hmac, :sha256, secret, body)
			|> Base.encode16(case: :lower)

			String.downcase(signature) == final_sig
	end
end
import hmac
import hashlib
import secrets

def verify_hmac(body: str, signature: str, secret: str) -> bool:
	final_sig = hmac.new(
		secret.encode('utf-8'),
		body.encode('utf-8'),
		hashlib.sha256
	).hexdigest()

	return signature.lower() == final_sig
Was this page helpful?