Runtime verified Test scripts included

Syntax

GetJWT(secret, algorithm, jsonPayload)  →  string
3 arguments — exactly

Parameters

Name Type Required Description
secret string Yes Secret used to sign the token; the empty string aborts the page
algorithm string Yes HMAC algorithm name — HS256, HS384 or HS512, matched case-insensitively
jsonPayload string Yes Payload to encode, copied into the token untouched

Example

%%[
  VAR @secret, @payload, @token
  SET @secret = "sfmc-probe-secret-2026"
  SET @payload = '{"sub":"probe","n":7}'
  SET @token = GetJWT(@secret, "HS256", @payload)
]%%
%%=v(@token)=%%

Renders eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJwcm9iZSIsIm4iOjd9.308Bu1Ma_gFb0_8RNujndwB8xJh6n3myT7xZ3h7BNhE.

The usual reason to reach for it is handing a receiving system something it can check for tampering, most often on a link:

%%[
  VAR @claims, @token
  SET @claims = Concat('{"email":"', AttributeValue("EmailAddress"), '","iat":"', Now(), '"}')
  SET @token = GetJWT(@secret, "HS256", @claims)
]%%
<a href="https://example.org/preferences?t=%%=v(@token)=%%">Update your preferences</a>

The secret is written into the page source, so anyone who can read the page can mint tokens. Where Key Management is available, GetJWTByKeyName keeps the secret out of the code.

Return value

string — the token as three Base64url segments joined by dots: the encoded header, the encoded payload, and the signature.

There is no closed set of sentinel values to test for. Every accepted call returns a token, and every rejected one aborts the page instead of returning an error value — so the result can never be checked defensively after the fact.

Behaviour

The token matches an independent implementation byte for byte. For the payload {"sub":"probe","n":7} and the secret above, the page returned exactly the value computed outside Marketing Cloud with a standard HMAC-SHA256, 109 characters long. The same held for HS384 and HS512.

The header is generated from the algorithm argument and nothing else. Decoding the first segment on the page gave {"alg":"HS256","typ":"JWT"} — no key id, no extra claims.

Signing is deterministic. Two calls with identical arguments in the same render produced the identical token, compared on the page rather than by eye.

The encoding is Base64url, not standard Base64

Searching the token found no =, no + and no /, and an _ at position 75. The segments therefore use the URL-safe alphabet and carry no padding, which is what lets a token travel in a query string untouched. Do not run the segments through a standard Base64 decoder without translating - and _ back first.

The algorithm name is matched case-insensitively

Passing hs256 produced exactly the HS256 token, header included — the decoded header still reads "alg":"HS256". No source mentions this. Anything outside the three HMAC names is refused outright: RS256 and the invented HS999 each aborted the page with HTTP 422, as did an empty secret.

The payload is never parsed

Passing the plain string not json at all produced a valid token whose middle segment is simply the Base64url of that string. Nothing validates the payload as JSON, so a malformed payload ships silently and fails only at the receiving end.

Show test script
%%[
  VAR @b, @sec, @pl, @t1, @t2, @p1, @rest, @p2
  SET @b = RequestParameter("b")
  SET @sec = "sfmc-probe-secret-2026"
  SET @pl = '{"sub":"probe","n":7}'

  /* known-good control: renders on every request, so a run of HTTP 422s
     can be told apart from a broken deploy */
  OutputLine(Concat("CTRL=[", Base64Encode("Man"), "]"))

  /* the token for a fixed payload and secret, its three segments, the
     decoded header, the base64url alphabet, and determinism */
  IF @b == "safe" THEN
    OutputLine(Concat("--- safe start ---"))
    SET @t1 = GetJWT(@sec, "HS256", @pl)
    OutputLine(Concat("T1=[", @t1, "]"))
    OutputLine(Concat("T1LEN=[", Length(@t1), "]"))
    SET @t2 = GetJWT(@sec, "HS256", @pl)
    IF @t1 == @t2 THEN
      OutputLine(Concat("DETERMINISTIC=[yes]"))
    ELSE
      OutputLine(Concat("DETERMINISTIC=[no]"))
    ENDIF
    SET @p1 = IndexOf(@t1, ".")
    SET @rest = Substring(@t1, Add(@p1, 1), Subtract(Length(@t1), @p1))
    SET @p2 = IndexOf(@rest, ".")
    OutputLine(Concat("SEG1=[", Substring(@t1, 1, Subtract(@p1, 1)), "]"))
    OutputLine(Concat("SEG2=[", Substring(@rest, 1, Subtract(@p2, 1)), "]"))
    OutputLine(Concat("SEG3=[", Substring(@rest, Add(@p2, 1), Subtract(Length(@rest), @p2)), "]"))
    OutputLine(Concat("HEADER=[", Base64Decode(Substring(@t1, 1, Subtract(@p1, 1))), "]"))
    OutputLine(Concat("HAS_EQ=[", IndexOf(@t1, "="), "] HAS_PLUS=[", IndexOf(@t1, "+"), "] HAS_SLASH=[", IndexOf(@t1, "/"), "]"))
    OutputLine(Concat("HAS_USCORE=[", IndexOf(@t1, "_"), "]"))
    OutputLine(Concat("--- safe done ---"))
  ENDIF

  /* the two longer HMAC variants */
  IF @b == "hs384" THEN
    OutputLine(Concat("--- hs384 start ---"))
    OutputLine(Concat("T=[", GetJWT(@sec, "HS384", @pl), "]"))
    OutputLine(Concat("--- hs384 done ---"))
  ENDIF

  IF @b == "hs512" THEN
    OutputLine(Concat("--- hs512 start ---"))
    OutputLine(Concat("T=[", GetJWT(@sec, "HS512", @pl), "]"))
    OutputLine(Concat("--- hs512 done ---"))
  ENDIF

  /* the algorithm name is matched case-insensitively */
  IF @b == "lower" THEN
    OutputLine(Concat("--- lower start ---"))
    OutputLine(Concat("T=[", GetJWT(@sec, "hs256", @pl), "]"))
    OutputLine(Concat("--- lower done ---"))
  ENDIF

  /* the payload is never parsed - a plain string is signed as readily */
  IF @b == "nonjson" THEN
    OutputLine(Concat("--- nonjson start ---"))
    OutputLine(Concat("T=[", GetJWT(@sec, "HS256", "not json at all"), "]"))
    OutputLine(Concat("--- nonjson done ---"))
  ENDIF

  /* each of the three branches below aborts the page - fetch alone */
  IF @b == "rs256" THEN
    OutputLine(Concat("--- rs256 start ---"))
    OutputLine(Concat("T=[", GetJWT(@sec, "RS256", @pl), "]"))
  ENDIF

  IF @b == "bogus" THEN
    OutputLine(Concat("--- bogus start ---"))
    OutputLine(Concat("T=[", GetJWT(@sec, "HS999", @pl), "]"))
  ENDIF

  IF @b == "emptysecret" THEN
    OutputLine(Concat("--- emptysecret start ---"))
    OutputLine(Concat("T=[", GetJWT("", "HS256", @pl), "]"))
  ENDIF
]%%

Availability

Platform Available
Marketing Cloud Engagement Yes
Marketing Cloud Next No

See also