Data Consistency with Webhook Functions
Tags: yext, typescript, webdev, webhooks
Published: 2022-06-21 14:30:19

In my [previous post](https://dev.to/yext/enriching-a-knowledge-graph-using-connectors-and-functions-134m), I explained how I was able to edit the primary photo for each entity in my Knowledge Graph utilizing a [Typescript function plugin](https://hitchhikers.yext.com/guides/local-function-development-with-deno/06-deploy-to-production/?utm_source=draftdev&utm_campaign=hitchhikers&utm_countent=enrich-kg-functions-and-connectors), the Cloudinary Image Transformation API, and the [Data Connectors framework](https://hitchhikers.yext.com/modules/kg139-data-connectors/01-data-connectors-overview/?utm_source=draftdev&utm_campaign=hitchhikers&utm_countent=enrich-kg-functions-and-connectors). If my [Knowledge Graph](https://hitchhikers.yext.com/modules/io102-what-is-knowledge-graph/01-what-is-knowledge-graph/?utm_source=draftdev&utm_campaign=hitchhikers&utm_countent=enrich-kg-functions-and-connectors) continues to expand with new Beverage entities, I want to ensure that their new photos have the same format as all the existing photos.

With [Webhooks](https://hitchhikers.yext.com/docs/knowledgeapis/webhooks/), I was able to apply the same logic to create a newly computed field called Transformed Photo.

## Updating my Function Plugin

I didn’t need to change any of the existing code that I already wrote. I just needed to add a few things to make it compatible with a Webhook invocation.

First, I added `types.ts` for some type definitions used for validating the input to the Webhook function.

```jsx
// types.ts

export interface WebhookPayload {
  changedFields: ChangedFields;
  entityId: string;
  primaryProfile: PrimaryProfile;
  meta: {
    eventType: string;
  };
}

export interface PrimaryProfile {
  primaryPhoto: PrimaryPhoto;
}
export interface PrimaryPhoto {
  image: Image;
}
export interface Image {
  url: string;
}
export interface ChangedFields {
  fieldNames: string[];
}
```

In `api.ts`, I added a Knowledge API client function for editing the newly created entity.

```jsx
// api.ts

import axiod from "https://deno.land/x/axiod@0.23.2/mod.ts";

declare const YEXT_ACCOUNT_API_KEY: string;

export const editEntity = async (
  entityId: string,
  // deno-lint-ignore no-explicit-any
  data: Record<string, any>
) => {
  try {
    // using fetch instead of axios works too. I just prefer axios syntax.
    await axiod.put(
      `https://api-sandbox.yext.com/v2/accounts/me/entities/${entityId}`,
      data,
      {
        params: {
          api_key: YEXT_ACCOUNT_API_KEY,
          v: "20220322",
        },
      }
    );
  } catch (error) {
    if (error.response) {
      console.error(`${error.response.status}: ${error.response.data}`);
    }
    throw error;
  }

  console.log(`Edited entity ${entityId}`);
};
```

Lastly, I added the `removeBackgroundHook` function to `mod.ts` that checks if the primary photo is a changed field. If the primary photo field is updated, the function calls my `removeBackground` function to get the new Image URL, and then calls the edit entity API and updates a new field I added to the beverage entity called `c_transformedPhoto`.

```jsx
// mod.ts

export const removeBackgroundHook = async (payload?: WebhookPayload) => {
  if (payload?.changedFields.fieldNames.includes("primaryPhoto")) {
    console.log(`Transforming primaryPhoto for Entity ${payload?.entityId} `);

    const imageUrl = await removeBackground(
      `${payload.entityId}|${payload.primaryProfile.primaryPhoto.image.url}`
    );

    if (imageUrl) {
      await editEntity(payload.entityId, {
        c_transformedPhoto: { url: imageUrl },
      });
    }
  }
};
```

Before running `yext resources apply`, I updated my `_resource.json` to include the additional variables I am going to need to call the Edit Entity API.

```json
{
  "$id": "TransformImagePlugin",
  "$schema": "https://schema.yext.com/config/platform/plugin/v1",
  "globals": {
    "CLOUDINARY_API_KEY": "${{CLOUDINARY_API_KEY}}",
    "CLOUDINARY_API_SECRET": "${{CLOUDINARY_API_SECRET}}",
    "YEXT_ACCOUNT_API_KEY": "${{YEXT_ACCOUNT_API_KEY}}"
  }
}
```

## Adding a Webhook in the Developer Console

In the Developer Console of my account, I added a new Webhook to my existing app. The API Key that I uploaded along with my plugin needs permission to update entities so I needed to add Read/Write access to the Entities Knowledge API in the API Credentials screen.


![api credentials](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rifca7atd14tyd9yxv9v.png)

Then, I set the Webhook Type which determines when the hook should be trigged.


![webhook type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/udgl4zmx96ptwirm5c28.png)

After selecting the Webhook Type, I gave my new hook a name, description, and the function it should invoke when triggered.


![webhook settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1cyt5a0r1ej2doyljst.png)

To make sure my hook was working correctly, I added a new primary photo to an entity in my Knowledge Graph and then checked to see the c_transformedPhoto field changed.

{% embed https://vimeo.com/722576752/bc54af5784 %}

You can see that any time the Primary Photo field changes, I get a new value added to the Transformed Photo field. Whether I create or edit entities via the UI, CSV upload, Data connector, or API call, the webhook will be called to create a new transformed photo. Webhooks can also be configured to run based on a variety of other events and could be used to call an API rather than a function. 

You can find the complete Webhook function plugin code [here](https://github.com/YextSolutions/transform-image).