The Gearment Webhook API provides real-time notifications for critical order and product events—such as order completion, cancellation, shipping updates, stock changes, and variant modifications—across multiple payload versions to ensure seamless integration with your existing systems.
https://your-webhook-url/
Webhook events are one-way events sent to your app over HTTP to notify you when an event occured. Unlike events that are sent over Gateway connections, events sent over webhooks are not realtime or guaranteed to be in order.
While incoming webhooks are triggered by an external service, webhook events (i.e. outgoing webhooks) are triggered by events happening in Gearment. This means your app will need to set up a public URL where you can receive HTTP events, which is detailed in the preparing for events section.
You can configure only one webhook URL per webhook event and version.
When Gearment sends an alert payload to your webhook URL, we include a variety of headers that you can use to verify that the payload came from QuickAlerts. For additional security, you can implement signature validation, which is an optional way to verify the authenticity of payloads received by your webhook URL.
Header | Description |
---|---|
X-Connect-Timestamp | The timestamp when this payload was sent. |
X-Connect-Nonce | The nonce we used in the generation of the payload signature. |
X-Connect-Signature | The signature we generated for this payload. |
X-Connect-Client-Key | The key we use in the generation to sign |
The API key and signature is a value known only to you and Gearment. It is used to generate a signature that can be validated on your end to ensure the authenticity of the received payloads.
The HMAC-SHA256 signature is computed as follows:
<URL path> + <nonce> + <timestamp> + <base64-url-encoded body>
X-Connect-Signature
header.Below are code snippets demonstrating how to verify the signature for different programming languages. These examples assume that the webhook headers (timestamp
, nonce
, signature
, etc.) are already available as variables. Replace the clientSecret
constant with your Webhook Security Token.
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"io"
"net/http"
"strings"
)
func verifySignature(r *http.Request) bool {
timestamp := r.Header.Get("X-Connect-Timestamp")
nonce := r.Header.Get("X-Connect-Nonce")
signature := r.Header.Get("X-Connect-Signature")
clientKey := r.Header.Get("X-Connect-Client-Key")
clientSecret := "client_secret" // Replace with your actual secret
// Read and preserve the body
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
return false
}
// Reset r.Body so it can be read again later if needed
r.Body = io.NopCloser(strings.NewReader(string(bodyBytes)))
// Base64 URL encode the body
bodyEncoded := base64.URLEncoding.EncodeToString(bodyBytes)
// Build the signing string
signingString := r.URL.Path + nonce + timestamp + bodyEncoded
// Compute HMAC-SHA256
h := hmac.New(sha256.New, []byte(clientSecret))
h.Write([]byte(signingString))
expectedSignature := base64.URLEncoding.EncodeToString(h.Sum(nil))
// Constant-time comparison
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
NodeJS
// Replace with your actual secret
const clientSecret = '0TxSN1NN7A8_v3_AsbbCffjEPEchBewtuRHCUMvVHy0DOh422PaHrbAk5WccWW0F';
function verifySignature(req: Request): boolean {
const timestamp = req.header('X-Connect-Timestamp');
const nonce = req.header('X-Connect-Nonce');
const signature = req.header('X-Connect-Signature');
const clientKey = req.header('X-Connect-Client-Key');
const rawBody = req.body;
if (!timestamp || !nonce || !signature || !clientKey) {
return false;
}
if (!rawBody) {
return false;
}
const bodyEncoded = base64UrlEncode(rawBody.toString());
const signingString = req.path + nonce + timestamp + bodyEncoded;
const expectedSignature = generateSignature(signingString, clientSecret);
// Secure compare
const result = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
return result;
}
export function base64UrlEncode(input: string): string {
const encoded = typeof window === 'undefined'
? Buffer.from(input).toString('base64')
: btoa(unescape(encodeURIComponent(input)));
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function generateSignature(signingString: string, clientSecret: string): string {
const hmac = crypto.createHmac('sha256', clientSecret);
hmac.update(signingString);
const digest = hmac.digest();
// Base64 URL encode: replace '+' with '-', '/' with '_', and remove '=' padding
return digest.toString('base64').replace(/\+/g, '-').replace(/\//g, '_');
}
Java
private boolean verifySignature(HttpServletRequest request) throws IOException {
String timestamp = request.getHeader("X-Connect-Timestamp");
String nonce = request.getHeader("X-Connect-Nonce");
String signature = request.getHeader("X-Connect-Signature");
// Read raw body
String body = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A").next();
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
// Base64 URL encode the body
String bodyEncoded = Base64.getUrlEncoder().withoutPadding().encodeToString(bodyBytes);
// Build signing string
String signingString = request.getRequestURI() + nonce + timestamp + bodyEncoded;
try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKey);
byte[] hashBytes = hmacSha256.doFinal(signingString.getBytes(StandardCharsets.UTF_8));
String expectedSignature = Base64.getEncoder().encodeToString(hashBytes);
if (expectedSignature.equals(signature)) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return false;
}