Webhook Signatures
Webhook Signatures
Overview
Webhook signatures provide an additional layer of security for your integration. When enabled, each webhook request sent to your endpoint will include a signature header. You can verify this signature on your side to ensure that the payload was sent by MyMobileAPI and has not been tampered with.
How It Works
- When signing is enabled, we compute an HMAC-SHA256 hash of a canonical string (
v1:{timestamp}|{METHOD}|{url}|{requestBody}) using your webhook signature secret key. - This signature is included in a custom HTTP header in each webhook request.
- Your server can recalculate the signature using the received payload + headers + your secret key, and compare it against the signature header to verify authenticity.
Configuration
You can manage webhook signature keys directly from the Control Panel. Signatures apply to all webhooks in your account.
Activating Signatures
- Navigate to Settings → Webhooks.
- Click Activate Signatures and enter a Signature alias when prompted.
- A Signature Secret Key will be generated and displayed.
- Store this secret securely — it only be shown to you once, and you will need it to verify webhook requests.
Once activated, all webhook deliveries from MyMobileAPI will include a signature header generated using the active key.
Rotating Keys (Generate New Key)
If you need to rotate your secret key:
-
Open the Webhooks panel.
-
Select Generate New Key.
-
A new secret key is issued and shown to you.
-
All previous keys are immediately deactivated.
-
All new webhook signature headers will use the newly generated key.
Rotation is immediate. Any service still using an old key for verification will fail until updated.
Deactivating Signatures
You may disable signing at any time:
- Open the Webhooks panel.
- Click Deactivate Signatures.
When deactivated:
- No signature header will be included in webhook deliveries.
- Previously generated secret keys remain inactive and cannot be reused.
- You may reactivate signing at any time, at which point a new key will be generated.
Signature-Related Headers
When webhook signatures are enabled, the SMS Webhook Engine includes the following additional headers. These fields allow customers to verify which key was used, when the signature was generated, and whether a delivery attempt is a retry.
| Header Name | Description |
|---|---|
SmsWebhookEngine-Key-Id | The alias of the signing key used to generate the signature. Customers can compare this value with the key aliases configured on their side to ensure the expected key was used. |
SmsWebhookEngine-Timestamp | The Unix epoch timestamp (in seconds) at the exact moment the signature was generated. This value forms part of the canonical string and allows clients to validate signature freshness. |
SmsWebhookEngine-Retries | Indicates how many times the webhook has previously been attempted. 0 represents the initial delivery, 1 the first retry, and so on. |
SmsWebhookEngine-Signature | The computed HMAC signature for the request, following the format and canonical string rules described below. This header is the primary value used by clients to validate request authenticity. |
Signature Header Format
The signature included in each webhook request follows a standard versioned format. This allows us to evolve the signing strategy over time while ensuring backwards compatibility for integrators.
The signature is delivered in the SmsWebhookEngine-Signature HTTP header and has the following structure:
v1,hmac_sha256=<hex>
Where:
-
v1
Indicates the version of the signing scheme. -
hmac_sha256=<hex>
The hexadecimal-encoded HMAC-SHA256 signature computed over the canonical string described below.
This format mirrors common industry designs and is intentionally simple to parse and validate across different languages and frameworks.
Building the Digest (<hex>)
<hex>)The <hex> portion of the signature contains the HMAC-SHA256 digest of a canonical signing string. The receiver reconstructs this same string to validate the authenticity and integrity of the webhook request.
1. Construct the canonical String
The canonical string format: v1:{timestamp}|{METHOD}|{url}|{requestBody}
v1:{timestamp}|{METHOD}|{url}|{requestBody}Each component must be included exactly and without modification.
| Field | Description | Example |
|---|---|---|
timestamp | Unix epoch seconds at the moment the webhook is sent. Must match the value in the SmsWebhookEngine-Timestamp header. | 1761569497 |
METHOD | The HTTP method of the request. Only GET and POST are supported. | POST |
url | The full request URL, including any query parameters. | https://example.com/webhook?event=dlr |
requestBody | The raw JSON request body string, exactly as transmitted over the wire. No hashing, no pretty-printing, no minification. | {"id":3019843,"status":"DELIVRD"} |
Important:
- Use the exact byte-for-byte JSON string as it appears in the HTTP body.
- Do not parse and re-serialize the JSON before signing; field order, whitespace, or encoding changes will break signature verification.
2. Compute the HMAC-SHA256 Digest
Using the customer’s active signature secret key, compute:
HMAC_SHA256(secretKey, canonical)
The result must be encoded as a uppercase hexadecimal string. This hex string becomes the <hex> value in the signature header.
3. Final Signature Format
Once the digest is calculated, the header value is assembled as:
v1,hmac_sha256=<hex>
Example:
SmsWebhookEngine-Signature: v1,hmac_sha256=4A92F3B8D4C9E52F6C77B0DF9F0C2E15FE0E26A8D7D7EFC6E04A77E4C16AD923
Code Examples
1. JavaScript
import crypto from "crypto";
function computeMac({ b64Secret, timestamp, method, url, body }) {
const key = Buffer.from(b64Secret, "base64");
const canonical = `v1:${timestamp}|${method}|${url}|${body}`;
const mac = crypto
.createHmac("sha256", key)
.update(canonical, "utf8")
.digest("hex")
.toUpperCase();
return mac;
}2. PHP
function computeMac(string $b64Secret, int $timestamp, string $method, string $url, string $body): string
{
// Decode the base64 secret into raw bytes
$key = base64_decode($b64Secret, true);
if ($key === false) {
throw new InvalidArgumentException("Invalid base64 secret.");
}
// Build the canonical string
$canonical = "v1:$timestamp|$method|$url|$body";
// Compute HMAC-SHA256; raw_output = true gives raw bytes
$hmac = hash_hmac('sha256', $canonical, $key, true);
// Convert to upper-case hex
return strtoupper(bin2hex($hmac));
}3. Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class MacUtil {
public static String computeMac(String b64Secret, long timestamp, String method, String url, String body) {
try {
// Decode the base64 secret
byte[] keyBytes = Base64.getDecoder().decode(b64Secret);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "HmacSHA256");
// Build canonical string
String canonical = "v1:" + timestamp + "|" + method + "|" + url + "|" + body;
// Compute HMAC
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
byte[] hmacBytes = mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8));
// Convert to UPPERCASE hex
return toUpperHex(hmacBytes);
} catch (Exception e) {
throw new RuntimeException("Failed to compute MAC", e);
}
}
private static String toUpperHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02X", b)); // uppercase hex
}
return sb.toString();
}
}Updated 1 day ago
