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

Creates a new webhook for an actor 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 3 times
Submitted by jakub.drobnik222 Bun
Verified 88 days ago
1
import { ActorListSortBy, ApifyClient } from 'apify-client@^2.19.0';
2
import { createHash } from 'node:crypto';
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
type ActorSource = 'RECENTLY_USED_ACTORS' | 'APIFY_STORE_ACTORS';
24

25
export type DynSelect_actorId = string;
26
export async function actorId(actorSource: ActorSource, api_key?: ApifyApiKey, oauth_token?: Apify) {
27
  if (!api_key?.api_key && !oauth_token?.token) {
28
    return [{ value: '', label: 'Missing Apify API key or OAuth token' }];
29
  }
30

31
  const client = createClient(api_key, oauth_token);
32

33
  const mapActorToSelectOption = (actor: any) => {
34
    const optionName = actor.title
35
      ? `${actor.title} (${actor.username}/${actor.name})`
36
      : `${actor.username}/${actor.name}`;
37

38
    return {
39
      label: optionName,
40
      value: actor.id,
41
    };
42
  };
43

44
  try {
45
    if (actorSource === 'RECENTLY_USED_ACTORS') {
46
      const recentActors = await client.actors().list({
47
        limit: 100,
48
        offset: 0,
49
        sortBy: ActorListSortBy.LAST_RUN_STARTED_AT,
50
        desc: true,
51
      });
52
      return recentActors.items.map(mapActorToSelectOption);
53
    }
54

55
    const storeActors = await client.store().list({
56
      limit: 1000,
57
      offset: 0,
58
    });
59
    return storeActors.items.map(mapActorToSelectOption);
60
  } catch (error: any) {
61
    return [
62
      { value: '', label: `Failed to load actors: ${error.message || error}` },
63
    ];
64
  }
65
}
66

67
function prettifyEvent(e: string) {
68
  return e.replace(/^ACTOR\.RUN\./, '');
69
}
70

71
function generateIdempotencyKey(
72
  id: string,
73
  eventTypes: WebhookEventTypes[],
74
  requestUrl: string
75
): string {
76
  const sortedEventTypes = [...eventTypes].sort();
77
  const url = new URL(requestUrl);
78
  const pathname = url.pathname;
79
  const hash = createHash('sha256');
80
  hash.update(`${id}:${sortedEventTypes.join(',')}:${pathname}`);
81
  return hash.digest('hex');
82
}
83

84
const createClient = (api_key?: ApifyApiKey, oauth_token?: Apify): ApifyClient => {
85
  const token = oauth_token?.token ?? api_key?.api_key;
86
  if (!token) {
87
    throw new Error('Missing Apify API key or OAuth token');
88
  }
89

90
  return new ApifyClient({
91
    token: token,
92
    requestInterceptors: [
93
      (request) => {
94
        if (!request.headers) {
95
          request.headers = {};
96
        }
97
        request.headers['x-apify-integration-platform'] = 'windmill';
98
        return request;
99
      },
100
    ],
101
  });
102
};
103

104
export async function main(
105
  actorSource: ActorSource,
106
  apifyWebhookConfig: ApifyWebhookConfig,
107
  actorId: DynSelect_actorId,
108
  eventTypes: WebhookEventTypes[],
109
  api_key?: ApifyApiKey,
110
  oauth_token?: Apify,
111
) {
112
  if (!actorId) {
113
    return { error: 'Actor ID is required' };
114
  }
115

116
  const client = createClient(api_key, oauth_token);
117

118
  const idempotencyKey = generateIdempotencyKey(
119
    actorId,
120
    eventTypes,
121
    apifyWebhookConfig.url
122
  );
123
  const headersTemplate = JSON.stringify({
124
    Authorization: `Bearer ${apifyWebhookConfig.token}`,
125
  });
126

127
  // get actor to store name for later webhook deletion
128
  const actor = await client.actor(actorId).get();
129
  const actorName = actor && (actor.title || actor.name);
130
  const webhookEvents = eventTypes
131
    .map((event) => prettifyEvent(event))
132
    .join(', ');
133

134
  try {
135
    const response = await client.webhooks().create({
136
      requestUrl: apifyWebhookConfig.url,
137
      eventTypes: eventTypes,
138
      condition: {
139
        actorId: actorId,
140
      },
141
      idempotencyKey,
142
      headersTemplate,
143
      shouldInterpolateStrings: true,
144
      description: `${actorName}: ${webhookEvents}`,
145
      title: "Windmill integration"
146
    });
147

148
    return response;
149
  } catch (e: any) {
150
    return { error: `Failed to create a webhook. Reason: ${e.message}` };
151
  }
152
}
153