Skip to main content

Hashing functions — Marketing Cloud SSJS reference

SHA256, SHA1, MD5, HMAC — when each is the right answer in MC SSJS, the hex-vs-Base64 output trap, and why MD5 is still around for tracking pixels but not for anything that matters.

Reference·Last updated 2026-05-13·Drafted by Lira · Edited by German Medina

Hashing in SSJS shows up where you need a fixed-length fingerprint of input data: cache keys, idempotency tokens, signature verification on inbound webhooks, opaque identifiers that don't reveal the underlying data, and tracking pixels that need to look uniform. Marketing Cloud exposes the standard library through Platform.Function.*: SHA256, SHA1, MD5, plus their HMAC variants. The traps are about which algorithm to pick (MD5 is broken for security but still used for non-security IDs), what output format you get (hex string by default, but byte-equivalence questions arise when comparing across systems), and the binary-data limitation we already saw with Base64 — these functions hash strings, not bytes.

Official syntax

Platform.Load("Core", "1.1.5");

// === Plain hashes (returns lowercase hex string) ===

Platform.Function.MD5("hello world");
// → "5eb63bbbe01eeed093cb22bb8f5acdc3"

Platform.Function.SHA1("hello world");
// → "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"

Platform.Function.SHA256("hello world");
// → "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"

Platform.Function.SHA512("hello world");
// → 128-char lowercase hex (SHA-512)

// === Keyed hashes (HMAC) — for verifying signed payloads ===

// HMACSHA256(message, key, returnHex)
//   - message: string to sign
//   - key: secret key (string)
//   - returnHex: true → hex string, false → Base64 string
Platform.Function.HMACSHA256("payload", "secret-key", true);
// → "f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317"

Platform.Function.HMACSHA256("payload", "secret-key", false);
// → "+TILqRJSbbXIY1iuzXgZcJsiNJTNl1HG18uPZ7+8DhM="

// HMACSHA1 / HMACMD5 also exist with the same signature
Platform.Function.HMACSHA1("payload", "secret-key", true);
Platform.Function.HMACMD5("payload", "secret-key", true);

// === Common pattern: signed redirect token ===

var payload = subscriberKey + "|" + Now().getTime();
var signature = Platform.Function.HMACSHA256(payload, secretKey, true);
var token = Platform.Function.URLEncode(payload + "|" + signature);
// On the receiving end: split, recompute HMACSHA256(payload, secretKey),
// compare to provided signature in constant time.

The supported set:

| Function | Signature | Output | |---|---|---| | MD5(s) | string → string | 32-char lowercase hex | | SHA1(s) | string → string | 40-char lowercase hex | | SHA256(s) | string → string | 64-char lowercase hex | | SHA512(s) | string → string | 128-char lowercase hex | | HMACMD5(msg, key, hex?) | strings + boolean → string | hex (true) or Base64 (false) | | HMACSHA1(msg, key, hex?) | strings + boolean → string | hex (true) or Base64 (false) | | HMACSHA256(msg, key, hex?) | strings + boolean → string | hex (true) or Base64 (false) |

Reference:

What survives in production

Pick the right algorithm — security vs identity

The four hash families serve different purposes. Picking the wrong one ships a vulnerability that a code review usually catches but a deadline doesn't.

| Algorithm | Use for | Avoid for | |---|---|---| | MD5 | Cache keys, tracking pixel IDs, deduplication keys where the only requirement is "stable fingerprint" | Anything where collision matters: passwords, signatures, tamper detection | | SHA1 | Legacy compatibility (e.g., third-party APIs that still require SHA1 signatures) | New code — go SHA256 unless you have a reason | | SHA256 | Signatures, integrity checks, anything where collision resistance matters. The default for new code. | Truncating to less than 16 chars — partial hash collisions get plausible quickly | | SHA512 | Use when an upstream system requires it. Otherwise SHA256 is sufficient. | Performance-sensitive paths that don't need 512-bit output |

The discipline: SHA256 is the default. Use MD5 only when an upstream system requires it (some MarTech tracking pixel formats), and document the choice in a comment so the next reviewer doesn't flag it as a security bug.

HMAC* is for signing, plain hashes are for fingerprinting

A hash without a key is a fingerprint — anyone who knows the input can compute the same output. That's fine for cache keys and IDs, useless for verifying that a request came from you.

HMAC* adds a secret key to the calculation. The receiving end can verify the message hasn't been tampered with only if they have the same secret. This is how webhook signatures, signed redirect URLs, and API request authentication work.

// AT RISK — using a plain hash as a "signature" is no signature at all.
// Anyone who can see the URL can compute the same value and forge requests.
var token = Platform.Function.SHA256(subscriberKey + "|" + Now().getTime());
var url = "https://app.example.com/?sub=" + subscriberKey + "&t=" + token;

// CORRECT — HMAC with a secret. The signature can't be reproduced without
// knowing the key, so you can verify provenance on the receiving side.
var payload = subscriberKey + "|" + Now().getTime();
var sig = Platform.Function.HMACSHA256(payload, sharedSecret, true);
var url = "https://app.example.com/?sub=" + Platform.Function.URLEncode(payload)
        + "&sig=" + Platform.Function.URLEncode(sig);

The general rule: if the question is "did this come from us?", you need an HMAC. A plain hash answers "is this the same input as before?", which is a different question.

Hex vs Base64 output — match the receiving system

The HMAC functions take a third boolean for output format: true → hex, false → Base64. The hex form is twice as long but predictable; the Base64 form is more compact but has the URL-safety question we saw in Encoding. When the receiving system expects one specific format, use that — don't assume hex is always safer.

// Webhook receiver expects Base64 signature header
var sig = Platform.Function.HMACSHA256(body, webhookSecret, false);
HTTP.Post("https://hook.example.com/in", body, {
  "X-Signature": sig,           // Base64
  "Content-Type": "application/json"
});

// Vendor API expects hex signature in query string
var sig = Platform.Function.HMACSHA256(payload, vendorSecret, true);
var url = vendorEndpoint + "?payload=" + Platform.Function.URLEncode(payload)
        + "&sig=" + sig;        // hex, no URL encoding needed

When in doubt, check the receiving system's documentation for the exact expected format. Sending Base64 where hex is expected (or vice versa) doesn't fail visibly — it fails as "signature does not match", which is the same error message you'd get if your secret was wrong.

Verify HMAC signatures with constant-time comparison

When receiving a signed request and verifying, the comparison should be constant-time to avoid timing attacks. Naive === exits early on the first character mismatch and leaks information about how much of the signature was correct.

// AT RISK — exits early, leaks signature progress via timing
function verify(provided, expected) {
  return provided === expected;
}

// SAFER — constant-time comparison; does the same number of operations
// regardless of where the mismatch is
function constantTimeEqual(a, b) {
  if (a == null || b == null) return false;
  if (a.length !== b.length) return false;
  var diff = 0;
  for (var i = 0; i < a.length; i++) {
    diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
  }
  return diff === 0;
}

var expected = Platform.Function.HMACSHA256(receivedPayload, sharedSecret, true);
if (!constantTimeEqual(receivedSignature, expected)) {
  // reject the request
}

For low-volume internal use the timing attack risk is theoretical, but the code is short and the discipline is worth carrying.

Quick decision

Use Platform.Function.SHA256 when:

  • You need a fingerprint of input data and the output goes into a system you control or sign for. The default for new code.

Use Platform.Function.MD5 when:

  • An upstream system specifically requires MD5 (legacy tracking pixels, some MarTech vendor APIs). Document the legacy reason in a comment.

Use Platform.Function.HMACSHA256(msg, key, hex) when:

  • You're producing a signature that the receiving system will verify with the same secret.
  • You're verifying an inbound signature on a webhook or signed callback.

Pick the output format (hex vs Base64) based on:

  • What the receiving system expects. Hex is the safe default if no spec is given. Base64 is shorter but needs URL encoding when used in URLs.

Use constant-time comparison when:

  • Verifying inbound HMAC signatures. The function above (constantTimeEqual) is the standard pattern.

Don't use hashing when:

  • You actually need encryption (i.e., the data has to be reversible). Hashing is one-way; for two-way you'd reach for Platform.Function.EncryptSymmetric / DecryptSymmetric.

Related

  • Basics — supported language surface
  • Platform.Function — the namespace where these helpers live
  • Encoding functions — Base64 / URL encoding patterns that often pair with HMAC signing
  • WSProxy — webhook flows and outbound integrations where HMAC signatures appear
  • MC SSJS gotchas — see #9 (HTTP callouts) for the broader integration context

More SSJS reference pages incoming: Util / Variable · Style Guide.

Plus how-to snippets for common production patterns — DE add/update/upsert, error handling, callout pagination, etc.