# Configuring webhook

> Setup webhook to automatically import smartling translations back into Uniform

Source: https://docs.uniform.app/docs/integrations/translation-management/smartling/configuring-webhook

Setup webhook to automatically import Smartling translations back into Uniform.

> **Webhook has to be publicly available:**
>
> Smartling integration settings has **Webhook URL** input where you need to provide publicly avaiable url
> where Smartling is going to push completed translation job.
>
> You can find example of implementation in this [example](https://github.com/uniformdev/examples/tree/main/examples/tms-webhook-smartling).

## Setup

You would need [Smartling](https://docs.uniform.app/docs/integrations/translation-management/smartling/getting-started) integration to be installed and configured.
Integration settings page has a **Webhook URL** settings, which will be passed down as `callbackMethod` into Smartling's [**Create job** api method](https://api-reference.smartling.com/#tag/Jobs/operation/addJob)

You can find a full example of implementation in this [examples repo](https://github.com/uniformdev/examples/blob/main/examples/tms-webhook-smartling/src/pages/api/smartling-job-completed-webhook.ts)

Lets create a webhook that can handle composition or entry translations coming back from Smartling.

1. First we need Smartling and Uniform credentials:

   ```bash
   # Authentication to Smartling
   SMARTLING_ACCOUNT_ID=
   SMARTLING_PROJECT_ID=
   SMARTLING_USER_ID=
   SMARTLING_USER_SECRET=

   # Authentication to Uniform
   UNIFORM_API_KEY=
   UNIFORM_PROJECT_ID=
   ```

> **Uniform API Keys:**
>
> [Here](https://docs.uniform.app/docs/guides/apis#create-api-key) you can find how to create Uniform API Keys. Smartling integration requires at least **Create** and **Update** permissions for **Compositions / Entries / Assets**

2. We would also need Uniform and Smartling deps

   ```bash
   npm i @uniformdev/canvas @uniformdev/tms-sdk smartling-api-sdk-nodejs
   ```
3. Smartling Integration is using [Job Batches v.2 flow](https://help.smartling.com/hc/en-us/articles/360007812713-API-Flow-Example#job-batches-v2) sending JSON files for translations.
   So we will retrieve translated files back because they also contain some metadata required for us to correcly import back to Uniform
   Smartling will call our callback with `{ translationJobUid, type }` parameters when Translation Job changes its status. we only care about completed jobs

   ```ts
   const { type, translationJobUid } = req.query;

   if (type !== 'job.completed') {
       console.log('We only react on comleted jobs');
       res.status(200).send('We only react on comleted jobs');
       return;
   }
   ```
4. Lets retrieve latest completed batch from the job

   ```ts
   const apiBuilder = new SmartlingApiClientBuilder()
   .setBaseSmartlingApiUrl('https://api.smartling.com')
   .authWithUserIdAndUserSecret(userId!, userSecret!);

   const jobsClient = apiBuilder.build(SmartlingJobsApi);
   const jobBatchesClient = apiBuilder.build(SmartlingJobBatchesApi);

   const lastBatch = await jobBatchesClient.listBatches(
       projectId,
       new ListBatchesParameters({ translationJobUid, limit: 1, status: 'COMPLETED' })
   );

   const batchInfo = lastBatch.items[0];
   const batchDetails = await jobBatchesClient.getBatchStatus(projectId, batchInfo.batchUid);
   ```
5. Now we can load all translated files and start processing them

   ```ts
   const filesClient = apiBuilder.build(SmartlingFilesApi);

   const translationsStatus: Record<string, boolean> = {};

   for (const fileInfo of batchDetails.files) {
       const downloadParameters = new DownloadFileParameters({ retrievalType: 'published' });
       const file = await filesClient.downloadFile(
           projectId,
           fileInfo.fileUri,
           fileInfo.targetLocales[0].localeId,
           downloadParameters);

       try {
       const translationPayload = JSON.parse(file.toString());
       const uniformProjectId = translationPayload.metadata.uniformProjectId;
       const uniformReleaseId = translationPayload.metadata.uniformReleaseId;
       const uniformEntityType = translationPayload.metadata.entityType;
       const uniformEntityId = translationPayload.metadata.entity.id;

       // Now we have everything we need to use tms-sdk package helpers
       ...
   ```
6. Now lets merge translation into Uniform entities

_For the US region:_

```ts
const translationPayload = JSON.parse(file.toString());
const uniformProjectId = translationPayload.metadata.uniformProjectId;
const uniformReleaseId = translationPayload.metadata.uniformReleaseId;
const uniformEntityType = translationPayload.metadata.entityType;
const uniformEntityId = translationPayload.metadata.entity.id;

const canvasClient = new CanvasClient({
    projectId: uniformProjectId,
    apiKey: uniformAPIKey,
    bypassCache: true,
});

const { translationMerged } = await mergeTranslationToUniform({
    canvasClient,
    contentClient,
    translationPayload,
    updateComposition: async ({ canvasClient, composition }) => {
        await canvasClient.updateComposition(composition);
        return true;
    },
    onNotFound: ({ translationPayload }) => {
        const entityType = translationPayload.metadata.entityType;
        const entityId = translationPayload.metadata.entity.id;

        console.log(`skip: can not find ${entityType} (${entityId})`);
    },
    onNotTranslatedResult: ({ updated, errorKind, errorText }) => {
        if (!updated) {
        console.log('Translation has no updates');
        } else if (errorKind !== undefined) {
        console.warn(errorText || 'Unknown error');
        }
    },
});
```

_For the EU region:_

```ts
const translationPayload = JSON.parse(file.toString());
const uniformProjectId = translationPayload.metadata.uniformProjectId;
const uniformReleaseId = translationPayload.metadata.uniformReleaseId;
const uniformEntityType = translationPayload.metadata.entityType;
const uniformEntityId = translationPayload.metadata.entity.id;

const canvasClient = new CanvasClient({
    apiHost: 'https://eu.uniform.app',
    edgeApiHost: 'https://eu.uniform.global',
    projectId: uniformProjectId,
    apiKey: uniformAPIKey,
    bypassCache: true,
});

const { translationMerged } = await mergeTranslationToUniform({
    canvasClient,
    contentClient,
    translationPayload,
    updateComposition: async ({ canvasClient, composition }) => {
        await canvasClient.updateComposition(composition);
        return true;
    },
    onNotFound: ({ translationPayload }) => {
        const entityType = translationPayload.metadata.entityType;
        const entityId = translationPayload.metadata.entity.id;

        console.log(`skip: can not find ${entityType} (${entityId})`);
    },
    onNotTranslatedResult: ({ updated, errorKind, errorText }) => {
        if (!updated) {
        console.log('Translation has no updates');
        } else if (errorKind !== undefined) {
        console.warn(errorText || 'Unknown error');
        }
    },
});
```

7. If everything goes fine - lets close Smartling job:

   ```ts
   await jobsClient.closeJob(projectId, translationJobUid as string, new CloseJobParameters({}));

   res.status(200).json('Ok');
   ```
