Flag of Ukraine

Signature Authentication

As briefly mentioned in our concepts, Signature Authentication is a security measure that can prevent outsiders from tampering with your Assembly Instructions. It provides trust in untrusted environments.

Given that there are only two parties in the world that have your account's Auth Secret (Transloadit and you), we can leverage that to generate a cryptographic signature on both our ends for the data that we exchange. We compare signatures after receiving a message, and know this exact message could have only come from someone that has the secret.

Since the signature is calculated via one-way encryption, the signature itself is not a secret, and someone seeing it could not derive the Auth Secret used to generate it. That's great because signatures are generated by servers that can keep a secret safe, and then injected into web browsers that don't share that quality.

For creating Assemblies with Transloadit, your back-end could calculate a Signature that only covers certain parameters, authenticated users, and a timeframe that it deems legitimate usage. For instance, it would refuse to generate a signature for users that are not logged in. You could use any business logic on the server-side here to decide if you hand out a signature, or not, and Transloadit can be configured to reject any request for your Account that is not accompanied by a correct signature for the payload.

If you want to make Signature Authentication mandatory for all requests concerning your account:

  1. Go to the App Settings in your account.
  2. In the API Settings section, enable the Require a correct Signature option.
  3. Hit the Save button.

Note: Most back-end SDKs automatically use Signature Authentication when you supply your Auth Secret. So perhaps, just this introduction is all you need to know. If you are integrating Transloadit into untrusted environments, however, such as browsers (Uppy!), you'll want to continue reading to see how your back-end can supply signatures to it.

How to generate Signatures

So, how does this all look?

The typical params field when creating an Assembly without Signature Authentication is as follows:

  "auth": {
    "key": "23c96d084c744219a2ce156772ec3211"
  "steps": { ... }

The auth.key in this example is the Auth Key from API Credentials in your account.

To sign this request, the additional auth.expires field needs to be added. This adds it to our payload, which is protected by our signature. If someone would change it, Transloadit would reject the request as the signature no longer matches. You signed a different payload than the one we received. If the signature does match, then we will naturally compare and reject by date as instructed. This way, requests become very hard to indefinitely repeat by a third party that got a hold of this payload. Because even though our A+ grade HTTPS should already go a long way in preventing that, browser cache could be easier to snoop on.

The expires property must contain a timestamp in the (near) future. Use YYYY/MM/DD HH:mm:ss+00:00 as the date format, making sure that UTC is used for the timezone. For example:

  "auth": {
    "key": "23c96d084c744219a2ce156772ec3211",
    "expires": "2022/01/31 16:53:14+00:00"
  "steps": { ... }

To calculate the signature for this request:

  1. Stringify the above JavaScript object into JSON.
  2. Calculate an RFC 6234-compliant HMAC hex signature on the string, with your Auth Secret as the key, and SHA384 as the hash algorithm. Transloadit also supports other hash algorithms: SHA1, SHA256, and SHA512. However, SHA384 is the recommended option as it is not susceptible to length extension attack.
  3. Add a signature multipart POST field to your request (e.g., a hidden field in a form), with the signature created in step 2 prefixed with the name of the algorithm you are using (e.g., sha384:1234542648909876543321…).

Note: If your implementation uses a template_id instead of steps, there's no need to generate a signature for the Instructions that your Template contains. We should only sign communication payloads.

Signature Authentication is offered on requests that you send to Transloadit, but also the other way around. For instance, in Async Mode you will want to ensure that any Assembly Notifications that Transloadit sends to your back-end are actually coming from us. The example Node.js code in the Assembly Notifications docs illustrates this flow, and we have example code for generating signatures in different languages right below.

Example code for different languages

When you deal with JSON, please keep in mind that your language of choice might escape slashes. That is to say, it might turn occurrences of / into \/. We calculate the signatures on our end with unescaped slashes! Please make sure to remove backslashes from your JSON before calculating its signature.

If you use PHP for example, please check the JSON_UNESCAPED_SLASHES option of the json_encode function.

const crypto = require('crypto')

const utcDateString = (ms) => {
  return new Date(ms)
    .replace(/-/g, '/')
    .replace(/T/, ' ')
    .replace(/\.\d+\Z$/, '+00:00')

// expire 1 hour from now (this must be milliseconds)
const expires = utcDateString(Date.now() + 1 * 60 * 60 * 1000)
const authKey = 'YOUR_TRANSLOADIT_KEY'
const authSecret = 'YOUR_TRANSLOADIT_SECRET'

const params = JSON.stringify({
  auth: {
    key: authKey,
  // your other params like template_id, notify_url, etc.
const signature = crypto
  .createHmac('sha384', authSecret)
  .update(Buffer.from(params, 'utf-8'))

console.log(`${expires} sha384:${signature}`)
// expire 1 hours from now
$expires    = gmdate('Y/m/d H:i:s+00:00', strtotime('+1 hour'));

$params = json_encode([
  'auth' => [
    'key'     => $authKey,
    'expires' => $expires,
  'template_id' => 'YOUR_TRANSLOADIT_TEMPLATE_ID',
$signature = hash_hmac('sha384', $params, $authSecret);

echo $expires . " sha384:" . $signature . PHP_EOL;
require 'rubygems'
require 'openssl'
require 'json'

# expire one hour from now
expires     = (Time.now.utc + 1 * 60 * 60).strftime('%Y/%m/%d %H:%M:%S+00:00')
auth_key    = 'YOUR_TRANSLOADIT_KEY'

params = JSON.generate({
  :auth => {
    :key     => auth_key,
    :expires => expires,
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha384'), auth_secret, params)

puts(expires + " sha384:" + signature)
import hmac
import hashlib
import json
from datetime import datetime, timedelta

expires = (timedelta(seconds=60 * 60) + datetime.utcnow()).strftime("%Y/%m/%d %H:%M:%S+00:00")
params = {
    'auth': {
        'key': auth_key,
        'expires': expires,
    # your other params like template_id, notify_url, etc.

message = json.dumps(params, separators=(',', ':'))
signature = hmac.new(auth_secret.encode('utf-8'),

print(expires, "sha384:" + signature)
package com.transloadit.sdk;

import org.apache.commons.codec.binary.Hex;
import org.joda.time.Instant;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class JavaSignature {
    public static void main(String[] args) {
        String authKey = "YOUR_TRANSLOADIT_KEY";
        String authSecret = "YOUR_TRANSLOADIT_SECRET";
        String templateId = "YOUR_TRANSLOADIT_TEMPLATE_ID";
        DateTimeFormatter formatter = DateTimeFormat
                .forPattern("Y/MM/dd HH:mm:ss+00:00")
        String expiry = formatter.print(Instant.now().plus(60 * 60 * 1000));

        String messageFormat = "{\"auth\":{\"key\":\"%s\",\"expires\":\"%s\"},\"template_id\":\"%s\"}";
        String message = String.format(messageFormat, authKey, expiry, templateId);

        byte[] kSecret = authSecret.getBytes(Charset.forName("UTF-8"));
        byte[] rawHmac = HmacSHA384(kSecret, message);
        byte[] hexBytes = new Hex().encode(rawHmac);

        System.out.println(expiry + " sha384:" + new String(hexBytes, Charset.forName("UTF-8")));

    private static byte[] HmacSHA384(byte[] key, String data) {
        final String ALGORITHM = "HmacSHA384";
        Mac mac;

        try {
            mac = Mac.getInstance(ALGORITHM);
            mac.init(new SecretKeySpec(key, ALGORITHM));
            return mac.doFinal(data.getBytes(Charset.forName("UTF-8")));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
20% off any plan for the Uppy community
Use the UPPY20 code when upgrading.
Sign up
20% off any plan for the tus community
Use the TUS20 code when upgrading.
Sign up
Product Hunt
20% off any plan for Product Hunters
Use the PRH20 code when upgrading.
Sign up