{{ ... }} expression, so an HTTP step can sign its own request without a Code step.
Functions
algorithm is one of md5, sha1, sha224, sha256, sha384, sha512 — dashes and case are ignored, so SHA-256 works. sha1 and md5 are there because some vendors still require them.
encoding is hex, base64 or base64url.
key_encoding says how to read the key before signing: utf8 (default), hex or base64. Reach for it when a vendor issues a hex- or base64-encoded signing key — signing its printable form instead of the bytes it stands for produces a signature that never verifies — or when chaining HMACs, as AWS does.
For the timestamp these schemes want, use {{ $floor($millis() / 1000) }} (Unix seconds).
Sign the bytes you actually send
The signature has to cover the request body byte for byte. With Body type: JSON, the step serializes your object on the way out, so a signature computed over a string you built separately will not match what is sent. Build the body once as a string, sign that same string, and send it with Body type: Raw:1
Compute the parts once, in a Transform step
Timestamp and nonce must be identical in the signed string and in the headers.
$millis() and $uuid() return something new on every call, so evaluating them twice gives you two different values and a signature that cannot verify.2
Sign them in the header
3
Send the same string as a raw body
Body type: Raw, body:
{{ sign.body }}, with Content-Type: application/json set by hand.Recipes
Each of these follows a vendor’s published scheme. Check the vendor’s docs for the exact canonical string — they differ in ways that matter. Stripe / Svix style —timestamp.body, hex:
v0: prefix, hex, with the timestamp in its own header:
Use
$credential(...), not $secret(...). $secret() resolves only inside a credential’s own refresher workflow and fails the step anywhere else.key_encoding is for:
x-amz-content-sha256: {{ $hash(sign.body) }}.
That is the signing key only. The full scheme then signs a canonical request — method, path, sorted query string, the signed headers in lowercase order, and the payload hash — which you have to assemble in the Transform step alongside the body, since the step does not expose the URL it finally sends. If you need all of SigV4 rather than one signed header, a Code step with botocore is less work.
Notes
- Signing keys belong in credentials, not pasted into a step. A credential’s value is redacted out of the stored run record; a key typed into a header is not.
- The signature itself is recorded in the run’s request log. That is intended — a signature is not secret, and seeing it is how you debug a rejected request. Redaction is exact-match and cannot follow a value through a hash, so do not build a signature over a secret you would rather not see derived.
- Verifying an inbound signature is not supported. These functions sign outbound requests. A trigger receives a parsed payload, not the raw bytes a signature would have to be checked against.

