Skip to main content
Version: v0.2b

Securing the Endpoint

Circuit provides a way for you to be sure that the message you are receiving originated from us and not from some third-party.

info

This step is optional, but we highly recommend you implement it.

The 'circuit-signature' header

Circuit includes, on every request, a header called circuit-signature. This header should be used to assert that the message your webhook is handling is indeed from Circuit and not from a third party.

The header contains a signature generated by using HMAC with your webhook secret as the key and the raw JSON payload of the request body as the message with the SHA256 hash function.

Obtaining your webhook secret

Secret uses a webhook secret to sign all the requests sent to your endpoint. This secret consists of 32 characters. For example: 7fd4eb15359c04280311116c6c597041

To obtain your webhook secret do the following:

  1. Go to your API settings page and reveal the webhook secret:

    Reveal Secret
  2. Then copy it and store it safely:

    Copy Secret
caution

You should never share this secret. This is unique for your team and if some malicious third-party obtained it, they could forge requests to your endpoint.

If you think your secret has been compromised, Circuit has the option to regenerate it via the settings page.

Securing your endpoint with the 'signature-header'

To secure your endpoint you need to validate that the message signature you received is the same if you recalculate it from the raw payload, using your signing key.

We present here some reference implementations:

// Using express

const express = require("express");
const bodyParser = require("body-parser");
const crypto = require('crypto')

const router = express.Router();
const app = express();

app.use(bodyParser.json());
app.use(bodyParser.text({type: '*/*'}));

router.post(/handle’,(request,response) => {
// validating the request
const circuitSignature = request.get('circuit-signature');
if (!receivedSignature) {
throw HttpError(400, 'Message invalid')
}

// The shared webhook secret obtained from our website
const secret = 'asdf...'

// Need to retrieve the body in the same format as it was received, don't use
// JSON.decode on an object as it may create a different string of characters
// than the one actually received. Use the unparsed body.
const message = body.raw

// Build an expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex')

// Compare the expected signature with the received one. It is recommended to
// use a timing safe compare function to prevent timing attacks.
if (
expectedSignature.length !== receivedSignature.length ||
!crypto.timingSafeEqual(
Buffer.from(expectedSignature, 0),
Buffer.from(receivedSignature, 0),
)
) {
throw HttpError(400, 'Message invalid')
}

// Now you can safely process the rest of the request here
// ...

return;
});

app.use("/", router);