Receiving Translations with Webhooks

Summary

This API call enables you to receive translations once items reach a published status for a given target language.

You configure a translation callback URL or webhook for your account in the Livewords application. A POST request is sent for each item for each target configured.

Screen_Shot_2017-11-06_at_14.20.52.png

Callback URL Format

The callback URL should be an absolute URL. The callback URL is used as a base URL for a relative URL /{target- language} used for an actual POST request for a given target language.

Example Requests

Suppose the callback URL for the source feed products is defined to be

https://box5e8f.cloud.acme.com/products

Let's say the Hoodie item has reached published status for Dutch. The following POST request is sent. In the examples below we have omitted headers for authentication purposes like X-Timestamp, X-Tokenand X-Signature.

POST https://box5e8f.cloud.acme.com/products/nl
Content-Type: text/html

<?xml version="1.0" encoding="UTF-8"?>
<product id="11" title="Hoodie">
  <title>Trui met capuchon</title>
  <short-title>Korte titel</short-title>
  <description>Deze trui met capuchon is blauw en heeft strepen</description>
</product>

and the following is sent when Hoodie is published in French.

POST https://box5e8f.cloud.acme.com/products/fr-FR
Content-Type: text/html

<?xml version="1.0" encoding="UTF-8"?>
<product id="11" title="Hoodie">
  <title>Sweat a capuche</title>
  <short-title>Titre courte</short-title>
  <description>Ce sweat a une capuche et des bandes</description>
</product>

Testing

Use RequestBin to test and inspect callback requests coming from your LiveWords account.

Security Considerations

Handling Errors

LivewordsFlow will listen for the following codes from your HTTP endpoint and react accordingly.

  • If LivewordsFlow receives a 2** (Success) code it will determine the webhook POST is successful and not retry.

  • For any other code, Livewords will retry POSTing for 24 hours.
    After 24 hours LivewordsFlow will mark the translation memory as inactive and the administrator will need to re-enable it manually.

 

Securing Requests to Callback URLs

To ensure the authenticity of requests to callback URLs configured, LivewordsFlow signs them and posts the signature along with other webhook request parameters. The request contains the following 3 headers to prove that the request is authentic.

Header

Type

Description

X-Timestamp

Integer

The epoch timestamp, i.e. the number of seconds passed since January 1, 1970

X-Token

String

A randomly generated string of minimum length 1 and maximum length 50

X-Signature

String

String with hexadecimal digits generated by the HMAC algorithm

To verify the webhook is authentic and must have originated from LivewordsFlow you need to perform these steps.

  1. Concatenate X-Timestamp and X-Token values.

  2. Encode the resulting string with the HMAC algorithm. You are using your LivewordsFlow API key as the HMAC key with SHA256 digest mode.

  3. Compare the HMAC-encoded string (in hexadecimal representation) to the value of the X-Signature header. If they are identical the request is authentic.

Optionally, you could cache the token value locally and not honor any subsequent request with the same token. This will prevent replay attacks. Another optional check could be to verify if the timestamp is close to the current time.

Example Code (Java)

Here are all authentication headers of an example request.

X-Timestamp: 1426699381062
X-Token: 3up2mmukv2ecmbc4b4fmds9675qru5yed1h30se6le7l7sogdt
X-Signature: 328223a1d91564523b4cac64f50f5650deb3cab6477b48371950e9d8749882ed

Below is a Java code example of how to establish the authenticity of this request.

public class Main {
  public static void main(String[] args) {

    // The value of the header named "X-Timestamp" on the POST request
    long timestamp = Long.valueOf("1426699381062");

    // The value of the header named "X-Token" on the POST request
    String token = "3up2m...sogdt";

    // The value of the header named "X-Signature" on the POST request
    String signatureOnRequestHeader = "32822...882ed";

    // The LiveWords API Key of your account
    String apiKey = "my-example-api-key";

    String signature = createHMACSSignature(timestamp, token, apiKey);
    boolean authenticated = signature != null && signature.equals(signatureOnRequestHeader);

    System.out.println(String.format("Authentication result for signature [%s]: %s",
      signatureOnRequestHeader, String.valueOf(authenticated)));
  }

  public static String createHMACSSignature(long timestamp, String token, String apiKey) {
    try {
      Mac hmac = Mac.getInstance("HmacSHA256");
      SecretKeySpec secretKey = new SecretKeySpec(apiKey.getBytes("UTF-8"), "HmacSHA256");
      hmac.init(secretKey);

      String message = String.valueOf(timestamp) + token;
      byte[] hmacBytes = hmac.doFinal(message.getBytes("UTF-8"));

      return new BigInteger(1, hmacBytes).toString(16);

    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }
}

Example Code (Node)

Here is a Node.js example following exactly the same idea. We create a new signature using our separately defined function createHMACSSignature given the timestamp and token from the request and the LiveWords API key. We compare this newly created signature with the signature that was sent along with the incoming request. The request is authentic iff both signatures are equal.

var crypto = require("crypto");

// The value of the header named "X-Timestamp" on the POST request
var timestamp = "1426699381062";

// The value of the header named "X-Token" on the POST request
var token = "3up2m...sogdt";

// The value of the header named "X-Signature" on the POST request
var signatureOnRequestHeader = "32822...882ed";

// The LiveWords API Key of your account
var apiKey = "my-example-api-key";

var signature = createHMACSSignature(timestamp, token, apiKey);
var authenticated = (signature===signatureOnRequestHeader);
console.log("Authentication result for signature [%s]: %s", signatureOnRequestHeader, authenticated);

// Create a HMAC signature for strings timestamp, token and apiKey
//
function createHMACSSignature(timestamp, token, apiKey) {
  var hmac = crypto.createHmac("sha256", new Buffer(apiKey, "utf8"));
  var message = new Buffer(timestamp + token, "utf8");
  var hmacBytes = hmac.update(message).digest("hex");
  return hmacBytes;
}

The example code above explicitely converts token and apiKey (and even timestamp) from strings to Node’s binary representation Buffer using a UTF-8 encoding. The example would still work correctly if we had just used strings instead of new Buffer(…​) since Node’s default encoding (called binary) works ok since none of token, apiKey or timestamp are expected to contain multibyte characters.

Note on PHP

php provides the hash_hmac function it can be used simmilarly to the above examples. Note however that is does not work to do a string equals of the result. It returns a hex encoded string (as needed) but it does not discard leading zeros. (as java and node do.) If you want to do String comparison the X-signature needs to be padded, or leading zeros need to be stripped from the output of hash_hmac. Alternatively both X-signature and the output of hash_hmac can be compared as numbers.

 

 
Have more questions? Submit a request

Comments