Skip to main content

Callbacks

A “callback” is a POST request sent from MT API to your Callback URL when an event you are already registered for occurs. Example: When a document is translated with our MT API you get the document /:id needed to download.

Important

Callback URLs must be accessible from public internet. Otherwise our servers won't be able to reach your servers.


Thanks to callbacks, you don't need to send constant requests to our API to check for updates on your translation, as you are directly informed when the translation is finished.

Callback signature

LanguageWire MT API signs the callback request it sends to your endpoints. This allows you to verify that the callbacks were sent by LanguageWire MT API and not by a third party. The way the signature is generated depends on which authorization method was used when the document translation was started.

Verifying signature for translations started with a JWT token

The callback request is authorized using a JWT (Json Web Token) token. The token is included in the Authorization header of the callback request, with the "Bearer" prefix.

First you should verify that the token is valid (e.g. not expired) and issued by LanguageWire's IdP system. The token is signed with an assymetric key using RS256 algorithm. To validate the token, you have to first retrieve the public key from the IdP system. The key is accessible at https://idp.languagewire.com/realms/languagewire, under the public_key property.

When you decode the token (you can use a tool like jwt.io to manually inspect a token), you must find the following properties along the JWT payload:

{
"iss": "https://idp.languagewire.com/realms/languagewire",
"signature": "payload_signature",
"exp": 1629352800,
"iat": 1629349200
}
  • The iss property contains the issuer of the token. It must always be https://idp.languagewire.com/realms/languagewire.
  • The exp and iat properties are the expiration and issue time of the token, respectively. You should check that the token is not expired and that it was issued recently.
  • The signature property is the expected SHA-256 hash of the expected request body. You should compute the hash of the actual request body and compare it with the expected hash. If the actual hash does not match the expected hash, then the request body has been tempered with and the request must be rejected.
Important

Do not modify or format the body of the request. Even if a single character is changed the HMAC will be different.

A short example of how this can be achieved in Python + Django + authlib:

import hmac
from authlib.jose import JsonWebToken
from django.http import HttpRequest

def verify_callback_signature(request: HttpRequest):
auth_header = request.headers["Authorization"]
if not auth_header.startswith("Bearer "):
# callback is invalid
raise ...

jwt_token = auth_header.removeprefix("Bearer ")
public_key = (
"-----BEGIN PUBLIC KEY-----\n"
+ <public_key> # retrieve public key here
+ "\n-----END PUBLIC KEY-----"
)
jwt = JsonWebToken(["RS256"])
jwt_claims = jwt.decode(
jwt_token,
key=public_key,
claims_options={
"exp": {
"essential": True,
},
"iat": {
"essential": True,
},
"iss": {
"essential": True,
"value": "https://idp.languagewire.com/realms/languagewire",
},
},
)
jwt_claims.validate()

expected_signature = jwt_claims["signature"]
actual_signature = hashlib.new("sha256", request.content).hexdigest()

if actual_signature == expected_signature:
# callback is valid
else:
# callback is invalid

Verifying signature for translations started with an API key

Important

API keys are now deprecated and are only supported when using the v1 endpoints. Check our migration guide on how you can transition to using v2 endpoints.

LanguageWire MT API signs the callback request it sends to your endpoints by including a signature X-Signature in each call's header. This allows you to verify that the requests were sent by our API and not by a third party.

  • The signature is generated using a HMAC-SHA256.
  • It uses hexadecimal digits.
  • Example of a signature: 50e03ebe65be98bb8bf11ba2c892d54c079aca2b0d3b0162769c6d757a25434f.

To determine the expected signature:

  1. Compute the HMAC with the SHA256 hash function.
  • Use your API Key as the hash key, and use the raw request body string as the message.
Important

Do not modify or format the body of the request. Even if a single character is changed the HMAC will be different.


  1. Compare the signature in the header to the expected signature (in hexadecimal form).
  • To protect against timing attacks, use a constant-time string comparison to compare the expected signature.

  • Example of how this can be achieved in Python+Django:

import hmac
from django.http import HttpRequest

def verify_callback_signature(request: HttpRequest, secret_key: str):
signature = request.headers["x-signature"]
hash_obj = hmac.new(secret_key.encode(), request.body, "sha256")
actual_signature = hash_obj.hexdigest()
if hmac.compare_digest(actual_signature, signature):
# callback is valid
else:
# callback is invalid

Callback retries

Until a status code “200 OK Request has been successful” is received, MT API keeps retrying the request.

  • There are 3 retries in total distributed at certain intervals (a "randomised value" delay between 0.5 to 1.5 seconds).
  • If the maximum number of retries is reached, the error callback will be invoked with an error code instead.