Smore API Help

Webhook Signature

What is a Webhook Signature?

When you use the Webhook feature, you need to expose your endpoint on the internet so that Smore can send requests to your endpoint. However, when you expose your Webhook endpoint on the internet, anyone can send requests to your endpoint, which may lead to security issues. To allow you to verify whether the received requests are from Smore, we include a signature in the requests. You can use this signature to verify if the requests are from Smore.

How to Verify the Signature of a Webhook Request?

Obtaining the Webhook Secret

When you create a Webhook, you will receive a Webhook secret like this:

{ "webhookSecret": "GDzIn77nt83fhfcVQD8hWcqXY3jM49pc" }

You need to save this secret on your server and then use it to verify the signature of the requests at your Webhook endpoint.

Calculating the Signature

When you receive a Webhook request from Smore at your Webhook endpoint, you will see a field named X-Smore-Signature in the request header:

X-Smore-Signature: 761f9d282bf4cdc41b2698f3e4bf9cb4a99442714bc70292b61cdaa60fb911e0

You need to use your Webhook secret and the raw body of the request to calculate the signature, then compare the calculated signature with the one in the request header. The steps are as follows:

  1. Use the HMAC SHA256 algorithm with your Webhook secret to hash the raw body of the request.

  2. Convert the hash value to a hexadecimal string.

  3. Convert the hexadecimal string to lowercase.

  4. Compare the calculated signature with the signature in the request header. If both signatures are the same, then continue processing the request; otherwise, stop processing the request and return an error.

You can refer to the following example code:

import { createHmac } from 'node:crypto'; import express from 'express'; const app = express(); app.use(express.raw({ type: 'application/json' })); app.post('/webhook', (request, response) => { const signature = request.headers['x-smore-signature']; const rawBody = request.body.toString(); if (!verifySignature(signature, 'secret', rawBody)) { response.status(403).send('Invalid signature'); return; } // Do something with the payload }); function verifySignature(signature, secret, rawBody) { const expectedSignature = createHmac('sha256', secret) .update(rawBody) .digest('hex'); return signature === digest; }
package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "github.com/valyala/fasthttp" ) func verifySignature(signature string, secret string, rawBody []byte) bool { hmac := hmac.New(sha256.New, []byte(secret)) hmac.Write(rawBody) expectedSignature := hex.EncodeToString(hmac.Sum(nil)) return signature == expectedSignature } func webhookHandler(ctx *fasthttp.RequestCtx) { signature := string(ctx.Request.Header.Peek("X-Smore-Signature")) body := ctx.Request.Body() if !verifySignature(signature, "secret", body) { ctx.SetStatusCode(fasthttp.StatusForbidden) ctx.SetBodyString("Invalid signature") return } // Do something with the payload fmt.Fprintf(ctx, "OK") } func main() { fasthttp.ListenAndServe(":8080", webhookHandler) }
import hashlib import hmac from flask import Flask, request, abort app = Flask(__name__) @app.route('/webhook', methods=['POST']) def webhook(): signature = request.headers.get('X-Smore-Signature') raw_body = request.data if not verify_signature(signature, b'secret', raw_body): abort(403, 'Invalid signature') # Do something with the payload return 'OK' def verify_signature(signature, secret, raw_body): expected_signature = hmac.new(secret, raw_body, hashlib.sha256).hexdigest() return signature == expected_signature if __name__ == '__main__': app.run()
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.*; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.NoSuchAlgorithmException; import java.security.InvalidKeyException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.apache.commons.codec.binary.Hex; @SpringBootApplication @RestController public class WebhookApplication { public static void main(String[] args) { SpringApplication.run(WebhookApplication.class, args); } @PostMapping("/webhook") public ResponseEntity<String> webhook(@RequestBody String rawBody, @RequestHeader("X-Smore-Signature") String signature) { if (!verifySignature(signature, "secret", rawBody)) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid signature"); } // Do something with the payload return ResponseEntity.ok("OK"); } private boolean verifySignature(String signature, String secret, String rawBody) { try { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256"); sha256_HMAC.init(secret_key); String hash = Hex.encodeHexString(sha256_HMAC.doFinal(rawBody.getBytes())); return signature.equals(hash); } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException("Error while calculating HMAC", e); } } }

If the signature you calculate differs from the one received, you should stop processing the request and return an error. If the signature you calculate matches the one received, it confirms that the request is from Smore, and you can proceed with processing the request.

Last modified: 08 December 2023