Create Task Webhook
One script reply has been approved by the moderators Verified

Creates a new webhook for a task with settings provided. The response is the created webhook object.

Idempotency key is used to avaid duplication a webhook.

Created by jakub.drobnik222 88 days ago Picked 1 time
Submitted by jakub.drobnik222 Bun
Verified 88 days ago
1
import { createHash } from 'node:crypto';
2
import { ApifyClient } from 'apify-client@^2.19.0';
3

4
type ApifyApiKey = {
5
  api_key: string;
6
};
7

8
type Apify = {
9
  token: string;
10
};
11

12
type WebhookEventTypes =
13
  | 'ACTOR.RUN.SUCCEEDED'
14
  | 'ACTOR.RUN.FAILED'
15
  | 'ACTOR.RUN.TIMED_OUT'
16
  | 'ACTOR.RUN.ABORTED'
17

18
type ApifyWebhookConfig = {
19
  url: string;
20
  token: string;
21
};
22

23
export type DynSelect_taskId = string;
24
export async function taskId(api_key?: ApifyApiKey, oauth_token?: Apify) {
25
  if (!api_key?.api_key && !oauth_token?.token) {
26
    return [{ value: '', label: 'Missing Apify API key or OAuth token' }];
27
  }
28

29
  try {
30
    const client = createClient(api_key, oauth_token);
31

32
    const data = await client.tasks().list();
33
    const items = data?.items ?? [];
34

35
    return items.map((task: any) => ({
36
      value: task.id,
37
      label: task.title || task.name || task.id,
38
    }));
39
  } catch (error: any) {
40
    return [
41
      { value: '', label: `Failed to load tasks: ${error.message || error}` },
42
    ];
43
  }
44
}
45

46
function prettifyEvent(e: string) {
47
  return e.replace(/^ACTOR\.RUN\./, '');
48
}
49

50
function generateIdempotencyKey(
51
  id: string,
52
  eventTypes: WebhookEventTypes[],
53
  requestUrl: string
54
): string {
55
  const sortedEventTypes = [...eventTypes].sort();
56
  const url = new URL(requestUrl);
57
  const pathname = url.pathname;
58
  const hash = createHash('sha256');
59
  hash.update(`${id}:${sortedEventTypes.join(',')}:${pathname}`);
60
  return hash.digest('hex');
61
}
62

63
const createClient = (api_key?: ApifyApiKey, oauth_token?: Apify): ApifyClient => {
64
  const token = oauth_token?.token ?? api_key?.api_key;
65
  if (!token) {
66
    throw new Error('Missing Apify API key or OAuth token');
67
  }
68

69
  return new ApifyClient({
70
    token: token,
71
    requestInterceptors: [
72
      (request) => {
73
        if (!request.headers) {
74
          request.headers = {};
75
        }
76
        request.headers['x-apify-integration-platform'] = 'windmill';
77
        return request;
78
      },
79
    ],
80
  });
81
};
82

83
export async function main(
84
  apifyWebhookConfig: ApifyWebhookConfig,
85
  taskId: DynSelect_taskId,
86
  eventTypes: WebhookEventTypes[],
87
  api_key?: ApifyApiKey,
88
  oauth_token?: Apify,
89
) {
90
  if (!taskId) {
91
    return { error: 'Task ID is required' };
92
  }
93

94
  const client = createClient(api_key, oauth_token);
95

96
  const idempotencyKey = generateIdempotencyKey(
97
    taskId,
98
    eventTypes,
99
    apifyWebhookConfig.url
100
  );
101
  const headersTemplate = JSON.stringify({
102
    Authorization: `Bearer ${apifyWebhookConfig.token}`,
103
  });
104

105
  // get task to store name for later webhook deletion
106
  const task = await client.task(taskId).get();
107
  const taskName = task && (task.title || task.name);
108
  const webhookEvents = eventTypes
109
    .map((event) => prettifyEvent(event))
110
    .join(', ');
111

112
  try {
113
    const response = await client.webhooks().create({
114
      requestUrl: apifyWebhookConfig.url,
115
      eventTypes: eventTypes,
116
      condition: {
117
        actorTaskId: taskId,
118
      },
119
      idempotencyKey,
120
      headersTemplate,
121
      shouldInterpolateStrings: true,
122
      description: `${taskName}: ${webhookEvents}`,
123
      title: "Windmill integration"
124
    });
125

126
    return response;
127
  } catch (e: any) {
128
    return { error: `Failed to create a webhook. Reason: ${e.message}` };
129
  }
130
}
131