Snap-in developmentReferences

Event sources

Supported event source types and their event types

Event source codeEvent source nameEvent type codeRemarksDocumentation URL
devrev-webhookDevRev webhookAll supported DevRev webhook event typesDevRev webhook events such as work_created and conversation_updatedDevRev Webhook
flow-eventsFlow EventsConfigurableEvent sources schedule and publish events directly from a snap-in.Scheduled events
flow-custom-webhookFlow Custom webhookConfigurableA generic webhook with a URL and configurable authentication logic.Generic event sources
timer-eventsTimer Eventstimer.tickSends a timer.tick event based on a configured schedule.Timer-based event sources
email-forwardEmail Forwardemail.receiveUsed for receiving emails forwarded from an email inbox/mailing list.Email-based event source

Event source instructions

You can provide custom instructions for each event source. These instructions are shown to the user when installing the snap-in. The instructions are written in markdown and can be provided in the setup_instructions field of the event source.

To provide dynamic instructions, you can access some metadata from event source objects. The following fields are accessible:

  • {{source.name}}: Name of the event source
  • {{source.trigger_url}}: Webhook URL
  • {{source.config.<field_name>}}: Fields from within the event source configuration

For example:

1event_sources:
2 organization:
3 - name: argocd-source
4 description: Events triggered by ArgoCD workflows
5 display_name: ArgoCD
6 type: flow-custom-webhook
7 setup_instructions: |
8 ## ArgoCD webhook
9 Your webhook URL is `{{source.trigger_url}}`. You can use it to configure the ArgoCD webhook.
10 For configuration instructions, refer to the [ArgoCD documentation](https://argo-cd.readthedocs.io/en/stable/operator-manual/notifications/services/webhook/).
11 config:
12 policy: |
13 package rego
14 output = {"event": event, "event_key": event_key} {
15 event := input.request.body
16 event_key := "argocd.workflow"
17 } else = {"response": response } {
18 response := {
19 "status_code": 400
20 }
21 }

DevRev Webhook

The devrev-webhook event source can be used to listen for events occuring on DevRev. These includes events like work_created and conversation_updated. The payload of the events is defined by the Webhooks Event Schema.

Webhook-event-request example:

1{
2 "account_created": {
3 "account": {
4 "created_by": {},
5 "created_date": "2023-01-01T12:00:00.000Z",
6 "display_id": "string",
7 "id": "string",
8 "modified_by": {},
9 "modified_date": "2023-01-01T12:00:00.000Z",
10 "display_name": "string",
11 "artifacts": [
12 null
13 ],
14 "custom_fields": {},
15 "custom_schema_fragments": [
16 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
17 ],
18 "description": "string",
19 "domains": [
20 null
21 ],
22 "external_refs": [
23 null
24 ],
25 "owned_by": [
26 {}
27 ],
28 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
29 "tags": [
30 {}
31 ],
32 "tier": "string"
33 }
34 },
35 "account_deleted": {
36 "id": "ACC-12345"
37 },
38 "account_updated": {
39 "account": {
40 "created_by": {},
41 "created_date": "2023-01-01T12:00:00.000Z",
42 "display_id": "string",
43 "id": "string",
44 "modified_by": {},
45 "modified_date": "2023-01-01T12:00:00.000Z",
46 "display_name": "string",
47 "artifacts": [
48 null
49 ],
50 "custom_fields": {},
51 "custom_schema_fragments": [
52 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
53 ],
54 "description": "string",
55 "domains": [
56 null
57 ],
58 "external_refs": [
59 null
60 ],
61 "owned_by": [
62 {}
63 ],
64 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
65 "tags": [
66 {}
67 ],
68 "tier": "string"
69 }
70 },
71 "conversation_created": {
72 "conversation": {
73 "created_by": {},
74 "created_date": "2023-01-01T12:00:00.000Z",
75 "display_id": "string",
76 "id": "string",
77 "modified_by": {},
78 "modified_date": "2023-01-01T12:00:00.000Z",
79 "description": "string",
80 "group": {},
81 "members": [
82 {}
83 ],
84 "messages": [
85 {}
86 ],
87 "metadata": {},
88 "owned_by": [
89 {}
90 ],
91 "stage": {},
92 "tags": [
93 {}
94 ],
95 "title": "string"
96 }
97 },
98 "conversation_deleted": {
99 "id": "string"
100 },
101 "conversation_updated": {
102 "conversation": {
103 "created_by": {},
104 "created_date": "2023-01-01T12:00:00.000Z",
105 "display_id": "string",
106 "id": "string",
107 "modified_by": {},
108 "modified_date": "2023-01-01T12:00:00.000Z",
109 "description": "string",
110 "group": {},
111 "members": [
112 {}
113 ],
114 "messages": [
115 {}
116 ],
117 "metadata": {},
118 "owned_by": [
119 {}
120 ],
121 "stage": {},
122 "tags": [
123 {}
124 ],
125 "title": "string"
126 }
127 },
128 "dev_user_created": {
129 "dev_user": {
130 "created_by": {},
131 "created_date": "2023-01-01T12:00:00.000Z",
132 "display_id": "string",
133 "id": "string",
134 "modified_by": {},
135 "modified_date": "2023-01-01T12:00:00.000Z",
136 "display_name": "string",
137 "display_picture": {},
138 "email": "string",
139 "full_name": "string",
140 "phone_numbers": [
141 null
142 ],
143 "state": "active",
144 "external_identities": [
145 {}
146 ]
147 }
148 },
149 "dev_user_deleted": {
150 "id": "string"
151 },
152 "dev_user_updated": {
153 "dev_user": {
154 "created_by": {},
155 "created_date": "2023-01-01T12:00:00.000Z",
156 "display_id": "string",
157 "id": "string",
158 "modified_by": {},
159 "modified_date": "2023-01-01T12:00:00.000Z",
160 "display_name": "string",
161 "display_picture": {},
162 "email": "string",
163 "full_name": "string",
164 "phone_numbers": [
165 null
166 ],
167 "state": "active",
168 "external_identities": [
169 {}
170 ]
171 }
172 },
173 "id": "string",
174 "part_created": {
175 "part": {
176 "created_by": {},
177 "created_date": "2023-01-01T12:00:00.000Z",
178 "display_id": "string",
179 "id": "string",
180 "modified_by": {},
181 "modified_date": "2023-01-01T12:00:00.000Z",
182 "artifacts": [
183 null
184 ],
185 "custom_fields": {},
186 "custom_schema_fragments": [
187 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
188 ],
189 "description": "string",
190 "name": "string",
191 "owned_by": [
192 {}
193 ],
194 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
195 "tags": [
196 {}
197 ]
198 }
199 },
200 "part_deleted": {
201 "id": "PROD-12345"
202 },
203 "part_updated": {
204 "part": {
205 "created_by": {},
206 "created_date": "2023-01-01T12:00:00.000Z",
207 "display_id": "string",
208 "id": "string",
209 "modified_by": {},
210 "modified_date": "2023-01-01T12:00:00.000Z",
211 "artifacts": [
212 null
213 ],
214 "custom_fields": {},
215 "custom_schema_fragments": [
216 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
217 ],
218 "description": "string",
219 "name": "string",
220 "owned_by": [
221 {}
222 ],
223 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
224 "tags": [
225 {}
226 ]
227 }
228 },
229 "rev_org_created": {
230 "rev_org": {
231 "created_by": {},
232 "created_date": "2023-01-01T12:00:00.000Z",
233 "display_id": "string",
234 "id": "string",
235 "modified_by": {},
236 "modified_date": "2023-01-01T12:00:00.000Z",
237 "display_name": "string",
238 "account": {},
239 "artifacts": [
240 null
241 ],
242 "custom_fields": {},
243 "custom_schema_fragments": [
244 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
245 ],
246 "description": "string",
247 "domain": "string",
248 "external_ref": "string",
249 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
250 "tags": [
251 {}
252 ]
253 }
254 },
255 "rev_org_deleted": {
256 "id": "REV-AbCdEfGh"
257 },
258 "rev_org_updated": {
259 "rev_org": {
260 "created_by": {},
261 "created_date": "2023-01-01T12:00:00.000Z",
262 "display_id": "string",
263 "id": "string",
264 "modified_by": {},
265 "modified_date": "2023-01-01T12:00:00.000Z",
266 "display_name": "string",
267 "account": {},
268 "artifacts": [
269 null
270 ],
271 "custom_fields": {},
272 "custom_schema_fragments": [
273 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
274 ],
275 "description": "string",
276 "domain": "string",
277 "external_ref": "string",
278 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
279 "tags": [
280 {}
281 ]
282 }
283 },
284 "rev_user_created": {
285 "rev_user": {
286 "created_by": {},
287 "created_date": "2023-01-01T12:00:00.000Z",
288 "display_id": "string",
289 "id": "string",
290 "modified_by": {},
291 "modified_date": "2023-01-01T12:00:00.000Z",
292 "display_name": "string",
293 "display_picture": {},
294 "email": "string",
295 "full_name": "string",
296 "phone_numbers": [
297 null
298 ],
299 "state": "active",
300 "artifacts": [
301 null
302 ],
303 "custom_fields": {},
304 "custom_schema_fragments": [
305 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
306 ],
307 "description": "string",
308 "external_ref": "string",
309 "is_verified": true,
310 "rev_org": {},
311 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
312 "tags": [
313 {}
314 ]
315 }
316 },
317 "rev_user_deleted": {
318 "id": "string"
319 },
320 "rev_user_updated": {
321 "rev_user": {
322 "created_by": {},
323 "created_date": "2023-01-01T12:00:00.000Z",
324 "display_id": "string",
325 "id": "string",
326 "modified_by": {},
327 "modified_date": "2023-01-01T12:00:00.000Z",
328 "display_name": "string",
329 "display_picture": {},
330 "email": "string",
331 "full_name": "string",
332 "phone_numbers": [
333 null
334 ],
335 "state": "active",
336 "artifacts": [
337 null
338 ],
339 "custom_fields": {},
340 "custom_schema_fragments": [
341 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
342 ],
343 "description": "string",
344 "external_ref": "string",
345 "is_verified": true,
346 "rev_org": {},
347 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
348 "tags": [
349 {}
350 ]
351 }
352 },
353 "sla_tracker_created": {
354 "sla_tracker": {
355 "created_by": {},
356 "created_date": "2023-01-01T12:00:00.000Z",
357 "display_id": "string",
358 "id": "string",
359 "modified_by": {},
360 "modified_date": "2023-01-01T12:00:00.000Z"
361 }
362 },
363 "sla_tracker_deleted": {
364 "id": "string"
365 },
366 "sla_tracker_updated": {
367 "sla_tracker": {
368 "created_by": {},
369 "created_date": "2023-01-01T12:00:00.000Z",
370 "display_id": "string",
371 "id": "string",
372 "modified_by": {},
373 "modified_date": "2023-01-01T12:00:00.000Z"
374 }
375 },
376 "tag_created": {
377 "tag": {
378 "created_by": {},
379 "created_date": "2023-01-01T12:00:00.000Z",
380 "display_id": "string",
381 "id": "string",
382 "modified_by": {},
383 "modified_date": "2023-01-01T12:00:00.000Z",
384 "allowed_values": [
385 null
386 ],
387 "description": "string",
388 "name": "string"
389 }
390 },
391 "tag_deleted": {
392 "id": "TAG-12345"
393 },
394 "tag_updated": {
395 "tag": {
396 "created_by": {},
397 "created_date": "2023-01-01T12:00:00.000Z",
398 "display_id": "string",
399 "id": "string",
400 "modified_by": {},
401 "modified_date": "2023-01-01T12:00:00.000Z",
402 "allowed_values": [
403 null
404 ],
405 "description": "string",
406 "name": "string"
407 }
408 },
409 "timeline_entry_created": {
410 "entry": {
411 "created_by": {},
412 "created_date": "2023-01-01T12:00:00.000Z",
413 "display_id": "string",
414 "id": "string",
415 "modified_by": {},
416 "modified_date": "2023-01-01T12:00:00.000Z",
417 "labels": [
418 null
419 ],
420 "object": "string",
421 "object_display_id": "string",
422 "object_type": "account",
423 "reactions": [
424 {}
425 ],
426 "thread": {},
427 "visibility": "external",
428 "artifacts": [
429 null
430 ],
431 "body": "string",
432 "body_type": "snap_kit",
433 "snap_kit_body": {
434 "body": {}
435 },
436 "snap_widget_body": [
437 {}
438 ]
439 }
440 },
441 "timeline_entry_deleted": {
442 "id": "don:core:<partition>:devo/<dev-org-id>:ticket/123:timeline_event/<timeline-event-id>"
443 },
444 "timeline_entry_updated": {
445 "entry": {
446 "created_by": {},
447 "created_date": "2023-01-01T12:00:00.000Z",
448 "display_id": "string",
449 "id": "string",
450 "modified_by": {},
451 "modified_date": "2023-01-01T12:00:00.000Z",
452 "labels": [
453 null
454 ],
455 "object": "string",
456 "object_display_id": "string",
457 "object_type": "account",
458 "reactions": [
459 {}
460 ],
461 "thread": {},
462 "visibility": "external",
463 "artifacts": [
464 null
465 ],
466 "body": "string",
467 "body_type": "snap_kit",
468 "snap_kit_body": {
469 "body": {}
470 },
471 "snap_widget_body": [
472 {}
473 ]
474 }
475 },
476 "timestamp": "2023-01-01T12:00:00.000Z",
477 "type": "account_created",
478 "verify": {
479 "challenge": "string"
480 },
481 "webhook_created": {
482 "webhook": {
483 "created_by": {},
484 "created_date": "2023-01-01T12:00:00.000Z",
485 "display_id": "string",
486 "id": "string",
487 "modified_by": {},
488 "modified_date": "2023-01-01T12:00:00.000Z",
489 "event_types": [
490 null
491 ],
492 "secret": "string",
493 "status": "active",
494 "url": "string"
495 }
496 },
497 "webhook_deleted": {
498 "id": "don:integration:<partition>:devo/<dev-org-id>:webhook/<webhook-id>"
499 },
500 "webhook_id": "don:integration:<partition>:devo/<dev-org-id>:webhook/<webhook-id>",
501 "webhook_updated": {
502 "webhook": {
503 "created_by": {},
504 "created_date": "2023-01-01T12:00:00.000Z",
505 "display_id": "string",
506 "id": "string",
507 "modified_by": {},
508 "modified_date": "2023-01-01T12:00:00.000Z",
509 "event_types": [
510 null
511 ],
512 "secret": "string",
513 "status": "active",
514 "url": "string"
515 }
516 },
517 "work_created": {
518 "work": {
519 "created_by": {},
520 "created_date": "2023-01-01T12:00:00.000Z",
521 "display_id": "string",
522 "id": "string",
523 "modified_by": {},
524 "modified_date": "2023-01-01T12:00:00.000Z",
525 "applies_to_part": {},
526 "artifacts": [
527 null
528 ],
529 "body": "string",
530 "custom_fields": {},
531 "custom_schema_fragments": [
532 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
533 ],
534 "owned_by": [
535 {}
536 ],
537 "reported_by": [
538 {}
539 ],
540 "stage": {},
541 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
542 "tags": [
543 {}
544 ],
545 "target_close_date": "2023-01-01T12:00:00.000Z",
546 "title": "string",
547 "developed_with": [
548 {}
549 ],
550 "priority": "p0"
551 }
552 },
553 "work_deleted": {
554 "id": "ISS-12345"
555 },
556 "work_updated": {
557 "work": {
558 "created_by": {},
559 "created_date": "2023-01-01T12:00:00.000Z",
560 "display_id": "string",
561 "id": "string",
562 "modified_by": {},
563 "modified_date": "2023-01-01T12:00:00.000Z",
564 "applies_to_part": {},
565 "artifacts": [
566 null
567 ],
568 "body": "string",
569 "custom_fields": {},
570 "custom_schema_fragments": [
571 "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>"
572 ],
573 "owned_by": [
574 {}
575 ],
576 "reported_by": [
577 {}
578 ],
579 "stage": {},
580 "stock_schema_fragment": "don:core:<partition>:devo/<dev-org-id>:custom_type_fragment/<custom-type-fragment-id>",
581 "tags": [
582 {}
583 ],
584 "target_close_date": "2023-01-01T12:00:00.000Z",
585 "title": "string",
586 "developed_with": [
587 {}
588 ],
589 "priority": "p0"
590 }
591 }
592}

Webhook Filters

To limit the events being sent to a webhook, you can also specify a jq query as a filter. This filter is applied on the webhook before the event is dispatched. If no filter is specified, then all events are sent.

The jq query can reference the event payload as . and the user creating the webhook as $user. The $user object only contains one string field: the id of the webhook creator.

The filter must return a boolean value. If the filter returns true, the event is dispatched to the webhook. If the filter returns false, the event is not dispatched to the webhook.

For example, to create an event source which listens for issue creation and comments on issues, you can define the event source as follows:

1event_sources:
2 organization:
3 - name: devrev-webhook
4 description: Events coming from DevRev for issues
5 display_name: DevRev webhook
6 type: devrev-webhook
7 config:
8 event_types:
9 - work_created
10 - timeline_entry_created
11 filter:
12 jq_query: |
13 if .type == "work_created" then
14 if (.work_created.work.type == "issue") then true
15 else false
16 end
17 elif .type == "timeline_entry_created" then
18 if (.timeline_entry_created.entry.type == "timeline_comment" and .timeline_entry_created.entry.object_type == "issue") then true
19 else false
20 end
21 else false
22 end

Scheduled events

flow-events are an event source that snap-ins can use to publish or schedule events directly. This event source can then be used to trigger another automation. This capability makes it ideal for building snap-ins that need to act later. For example, sending reminders to issues still in the Triage stage.

You create an event source of type flow-events, and from your snap-in, you call the DevRev API to schedule an event for this event source. The following example illustrates how this works:

manifest.yaml

1version: "2"
2
3service_account:
4 display_name: Test bot
5
6event_sources:
7 organization:
8 - name: devrev-webhook
9 description: Event coming from DevRev
10 display_name: DevRev
11 type: devrev-webhook
12 config:
13 event_types:
14 - work_created
15 - name: scheduled-events
16 description: Events scheduled from snap-ins
17 display_name: scheduled-events
18 type: flow-events
19
20functions:
21 - name: function_1
22 description: Function 1
23 - name: function_2
24 description: Function 2
25
26automations:
27 - name: Schedule an event 30 seconds after work is created
28 source: devrev-webhook
29 event_types:
30 - work_created
31 function: function_1
32 - name: Comment hello on scheduled work item
33 source: scheduled-events
34 event_types:
35 - custom:work-created-scheduled-event
36 function: function_2

In this example, the first automation schedules an event with a delay of 30 seconds for the event source scheduled-events.

The second automation then binds function_2 to this event source. So the scheduled event executes function_2 30 seconds after the work is created.

In function_1, you can publish the event as follows:

1 const url = "https://api.devrev.ai/event-sources.schedule";
2 const work = event.payload.work_created.work;
3 const delay_secs = 30;
4 const event_payload = {
5 "object_id": work.id,
6 "name" : work.created_by.full_name,
7 "delay": delay_secs,
8 }
9 const payload_bytes = Buffer.from(JSON.stringify(event_payload)).toString('base64');
10 const publish_at = new Date(Date.now() + 1000 * delay_secs).toISOString();
11
12 const req = {
13 "id": event.input_data.event_sources["scheduled-events"],
14 "payload": payload_bytes,
15 "event_type": "work-created-scheduled-event",
16 "publish_at": publish_at,
17 "event_key": `delayed-run-${work.id}`
18 }
19 const response = await axios.post(url, req, {
20 headers: {
21 'Content-Type': 'application/json',
22 authorization: event.context.secrets.service_account_token
23 }
24 });
25 console.log('response: ', response.data);

In function_2, you can use the payload of the scheduled event as follows:

1 const object_id = event.payload.object_id;
2 const name = event.payload.name;
3 const delay = event.payload.delay;
4 console.log(`Received payload with object_id: ${object_id}, name: ${name}, delay: ${delay}`);

The /event-sources.schedule API is currently in early access. For API details, see event-sources-schedule-event.

To cancel scheduled events, you can utilize the /event-sources.unschedule API. For API details, see event-sources-delete-scheduled-event.

Generic event sources

You can create a generic event source if you need an event source not supported by DevRev. A generic event source includes a webhook URL and developer-provided code on how to validate the events coming on the URL.

For example, let’s say you want to connect to a source that can publish events to a webhook URL, and it hashes the entire request payload with the shared secret and adds an HTTP header X-Signature-256 with value as the hash. Then, you can provide the custom code (currently in rego) to validate the payload with the same algorithm. You would also calculate the hash and verify with the value present in the header to avoid someone else tampering in between.

To create a generic event source in your manifest, you create an event source with the type flow-custom-webhook and config containing a key policy which contains the rego code.

The input passed to the policy is the following JSON:

1 {
2 "parameters": "<a JSON which contains the parameters you have specified in your event source's `config.parameters` field>",
3 "request": {
4 "method": "<HTTP method of the request (GET, POST.)>",
5 "query": "<key-value map of the query parameters in the request URL. The keys are case sensitive. This is of type map[string][]string.
6 for example, if the query string is `a=1&b=2&b=3&c[]=4&c[]=5`, then the value of this field is `{"a": ["1"], "b": ["2", "3"], "c[]": ["4", "5"]}`>",
7 "query_raw": "<The raw query string without the '?'.>",
8 "headers": "<key-value (string) map of the request headers. Duplicate values for same `key` are overridden.
9 Header keys are in Canonical-Header-Key format. For example, Content-Type, Accept-Encoding, and so on.>",
10 "body": "<JSON body of the HTTP request received on the webhook. If Content-Type is application/x-www-form-urlencoded, then the body is parsed in the same way as the `query` field.",
11 "body_raw": "<Raw body (bytes) of the HTTP request as a base64 string.>"
12 }
13 }

The policy must return an object output in the following format:

1 {
2 "event": "[optional]
3 A JSON representing the event to emit.
4 This is `input.request.body` in most cases.
5 If it is not present (or null), then the event is not published.",
6 "event_key": "[required and non-empty if `event` is non-null]
7 `string` to represent the type of the event.
8 For example, file-created, pipeline-failed and so on.
9 This `event_key` is prefixed with 'custom:' and published to DevRev.
10 So, Snap-ins that want to use this event should use the `event_type` as `custom:event-key` in their automations.",
11 "response": `[optional]
12 A JSON containing following fields:
13 {
14 "status_code": "[optional]
15 `integer` HTTP status code to return to the caller.
16 Only valid HTTP codes allowed.
17 Defaults to 200.",
18 "body": "[optional]
19 `string` or `JSON` for HTTP body to return to the client.",
20 "headers": "[optional]
21 `JSON` with each key and value of type string to be returned in the HTTP response."
22 }`
23 }

Example of a generic event source. This event source authenticates the incoming request by checking the header X-Signature-256.

1event_sources:
2 organization:
3 - name: custom-webhook
4 description: Events sent on a custom webhook connected to an external system
5 display_name: Custom external webhook
6 type: flow-custom-webhook
7 setup_instructions: |
8 ## Custom webhook
9 - You webhook URL is `{{source.trigger_url}}`.
10 - Your secret is `{{source.config.parameters.secret}}`.
11 - The webhook is triggered when you send a POST request to the URL.
12 - The request must have a header `X-Signature-256` with value as `HMAC-SHA256` of the request body using the secret as the key.
13 config:
14 policy: |
15 package rego
16 payload_as_string := base64.decode(input.request.body_raw) ## body_raw is a base64 encoded string
17 expected_signature := crypto.hmac.sha256(payload_as_string, input.parameters.secret)
18 output = {"event": event, "event_key": event_key} {
19 input.request.method == "POST"
20 expected_signature == input.request.headers["X-Signature-256"]
21 event := input.request.body
22 event_key := "my-event"
23 } else = {"response": response} {
24 response := {
25 "status_code": 401,
26 "body": "Unauthenticated"
27 }
28 }
29 parameters:
30 secret: "EnterYourRandomSecretHere"

The event_key output of the policy can be used to define an automation on the event custom:event_key. For example:

1automations:
2 - name: Run on custom webhook
3 source: custom-webhook
4 event_types:
5 - "custom:my-event"
6 function: function_1

Here, you are creating an event source with the name custom-webhook, and the policy validates that there should be a header X-Signature-256 with a value of HMAC-SHA256 of raw request body.

A sample curl command to trigger this event source manually is:

1curl -i -H 'X-Signature-256: f1809d5135917b311644058cf1994c5ff4898ad20dbf6e282c1433e6be4e2fe1' \
2-d '{"hello":"world"}' \
3https://api.devrev.ai/hidden/dev-orgs/DEV-123/event-source-webhooks/custom/d43fc297-03d7-4cbd-bdf9-044847788306

Timer-based event sources

Timer-based event sources can be created to send events based on intervals and cron schedules. In the following example, you have two event sources, one emits events daily at 12:00am, the other every hour (3600 seconds). In the event payload, you see the JSON field metadata you specified in the event source configuration.

1event_sources:
2 organization:
3 - name: daily-timer-source
4 description: Timer event source based on Cron expression
5 display_name: Timer source
6 type: timer-events
7 config:
8 cron: "0 0 * * *"
9 metadata:
10 event_key: daily_events
11
12 - name: hourly-events
13 description: Timer event source based on interval seconds
14 display_name: Timer source
15 type: timer-events
16 config:
17 interval_seconds: 3600
18 metadata:
19 event_key: hourly_events

The automation event type for timer events is timer.tick. To initiate an automation based on timer events, use the following syntax in manifest.yaml:

1automations:
2 - name: <name-of-automation>
3 source: <timer-event-name>
4 event_types:
5 - timer.tick
6 function: <func-name>

The payload contains the metadata fields you specified in the event source configuration. For example, {"event_key": "daily_events"} for the daily-timer-source.

Email-based event sources

You can create an email-based event source that can be used to write automations on top of emails. For this, a sample of the YAML is shown below:

- name: email-event-source
description: Event source which emits an event when an email is received.
display_name: Email events listener
type: email-forward
config:
allowlist:
- "test@company.com"
- "mycompanydomain"

The event type of the events emitted by this event source is email.receive.

When a snap-in using such an event source is deployed, the instructions show a unique email address. You have to set up forwarding in your original email inbox or Google Group to this email address. In the example above, if the forwarding address is v1.abc.xyz@hooks.devrev.ai, you should set up email forwarding from test@company.com to v1.abc.xyz@hooks.devrev.ai.

Only emails forwarded from the allowlist of emails/domains are emitted as events. Hence, this event source should be used for email forwarding only, not as a direct mailbox to which anyone can send their emails. Emails received (either directly or by forwarding) from senders who aren’t in the allowlist are dropped. There are a few exceptions to this like Google’s forwarding confirmation emails.

The allowlist can either contain an email address or an entire domain. Strict matching is done for domains, meaning that subdomains are not included.

The email protocol regarding forwarding is not very well defined. We’ve tested forwarding from common sources like Gmail, Google Groups, and Outlook Inbox. However, if you’re having issues with email forwarding on other providers, feel free to contact us.

Payload

Below is a sample payload of the events produced by the email event source:

1{
2 "emailBody": "test reply\r\n\r\nOn Thu, Sep 7, 2023 at 3:23 PM Test User <test@example.com> wrote:\r\n\r\n> test email body\r\n>\r\n",
3 "from": [
4 "test@example.com"
5 ],
6 "to": [
7 "test@company.com"
8 ],
9 "cc": [],
10 "bcc": [],
11 "htmlBody": "<div dir=\"ltr\"><div>test reply</div><div><br /></div><div class=\"gmail_quote\"><div dir=\"ltr\" class=\"gmail_attr\">On Thu, Sep 7, 2023 at 3:23 PM Test User &lt;<a href=\"mailto:test@example.com\">test@example.com</a>&gt; wrote:<br /></div><blockquote class=\"gmail_quote\" style=\"margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex\"><div dir=\"ltr\">test email body<br /></div>\r\n</blockquote></div></div>\r\n",
12 "inReplyToId": {
13 "stringValues": [
14 "jksdnfjnsflkdsfkjaabcdefghiuK8BA@mail.gmail.com"
15 ]
16 },
17 "messageId": "ABCdEfghijklmnopqrstuvwZHL+AB_XYZg@mail.gmail.com",
18 "rawBodyArtifactId": "don:core:dvrv-us-1:devo/802:artifact/296",
19 "referenceIds": {
20 "stringValues": [
21 "abcdabc123123123@mail.gmail.com"
22 ]
23 },
24 "artifactIds": [
25 "don:core:dvrv-us-1:devo/802:artifact/1"
26 ],
27 "replyTo": {
28 "stringValues": [
29 "test+123@example.com"
30 ]
31 },
32 "sentOn": "2023-09-07T09:56:37Z",
33 "subject": "Re: test email title"
34}

Below is a brief description of the fields:

  • emailBody: The plain text body of the email.
  • htmlBody: The HTML body of the email.
  • from: List of from email addresses of the email.
  • to, cc, bcc: List of recipient email addresses.
  • inReplyToId: Message ID of the email (if received mail is a reply to some other previous mail).
  • messageId: Message ID of the received email.
  • rawBodyArtifactId: Artifact ID for the raw email (in MIME format) received on the event source. You can use the artifact APIs to download this email if you need to.
  • artifactIds: Artifact IDs for attachments in the email.
  • replyTo: Reply-To header of the email.
  • sentOn: Timestamp when email was sent (in UTC).
  • subject: Subject of the email.