Skip to content
275 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Webhooks: every event, to your own addresses, signed and retried1//! The webhooks service: events, delivered to the addresses a repository or
2//! a workspace registers.
3//!
4//! Every event g1t publishes can be delivered: an HTTPS `POST` of JSON,
5//! signed with the webhook's secret in `X-G1t-Signature-256`, and retried
6//! with growing waits when the receiver does not answer with a 2xx. Each
7//! delivery is kept, with what was sent and what came back, and can be sent
8//! again.
9//!
10//! Mirrors `packages/contracts/src/webhooks.ts`.
11
12use serde::{Deserialize, Serialize};
13
14use crate::repos::RepoPath;
15use crate::{User, Viewer};
16
17/// Every event a webhook can be sent, in the order people are shown them.
Checks: statuses and check runs on every commit, for CI and integrations18pub const EVENT_TYPES: [&str; 88] = [
Webhooks: every event, to your own addresses, signed and retried19 "git.push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look20 "branch.renamed",
Webhooks: every event, to your own addresses, signed and retried21 "repo.created",
22 "repo.forked",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look23 "repo.updated",
24 "repo.visibility_changed",
25 "repo.renamed",
26 "repo.transferred",
27 "repo.default_branch_changed",
28 "repo.archived",
29 "repo.unarchived",
30 "repo.deleted",
31 "repo.restored",
32 "repo.purged",
33 "repo.collaborator_added",
34 "repo.collaborator_removed",
35 "repo.collaborator_role_changed",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar36 "team.created",
37 "team.edited",
38 "team.deleted",
39 "team.member_added",
40 "team.member_role_changed",
41 "team.member_removed",
42 "team.repo_added",
43 "team.repo_role_changed",
44 "team.repo_removed",
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge45 "ruleset.created",
46 "ruleset.updated",
47 "ruleset.deleted",
Webhooks: every event, to your own addresses, signed and retried48 "issue.opened",
49 "issue.updated",
50 "issue.assigned",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar51 "issue.labeled",
52 "issue.unlabeled",
53 "issue.milestoned",
54 "issue.demilestoned",
Webhooks: every event, to your own addresses, signed and retried55 "issue.closed",
56 "issue.reopened",
57 "comment.created",
58 "pull.opened",
59 "pull.ready",
60 "pull.updated",
61 "pull.merge_requested",
62 "pull.merged",
63 "pull.closed",
Events: review requests, assignments, stops and deployments are published64 "pull.assigned",
65 "pull.review_requested",
66 "pull.review_request_removed",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar67 "pull.labeled",
68 "pull.unlabeled",
69 "pull.milestoned",
70 "pull.demilestoned",
71 "pull.base_changed",
Events: review requests, assignments, stops and deployments are published72 "pull.stalled",
73 "pull.resumed",
Agents asked while not at work are woken to answer74 "agent.asked",
Webhooks: every event, to your own addresses, signed and retried75 "checks.completed",
Checks: statuses and check runs on every commit, for CI and integrations76 "status.created",
77 "check_run.created",
78 "check_run.completed",
79 "check_run.rerequested",
80 "check_run.requested_action",
81 "check_suite.completed",
82 "check_suite.rerequested",
Webhooks: every event, to your own addresses, signed and retried83 "review.completed",
Actions: workflow_run, workflow.completed, artifacts on the run page, Node 2484 "workflow.completed",
Events: review requests, assignments, stops and deployments are published85 "deployment.succeeded",
86 "deployment.failed",
Webhooks: every event, to your own addresses, signed and retried87 "queue.changed",
88 "session.appended",
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member89 "package.published",
90 "package.version_deleted",
91 "package.deleted",
92 "package.visibility_changed",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar93 "secret_scanning_alert.created",
94 "secret_scanning_alert.fixed",
95 "secret_scanning_alert.dismissed",
96 "secret_scanning_alert.reopened",
97 "secret_scanning.bypass_requested",
98 "secret_scanning.bypass_reviewed",
99 "code_scanning_alert.created",
100 "code_scanning_alert.fixed",
101 "code_scanning_alert.dismissed",
102 "code_scanning_alert.reopened",
103 "vulnerability_alert.created",
104 "vulnerability_alert.fixed",
105 "vulnerability_alert.dismissed",
106 "vulnerability_alert.reopened",
Webhooks: every event, to your own addresses, signed and retried107];
108
109/// What a webhook belongs to.
110#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
111#[serde(rename_all = "snake_case")]
112pub enum HookScope {
113 /// One repository's events.
114 Repo,
115 /// The events of every repository in a workspace.
116 Workspace,
117}
118
119#[derive(Clone, Debug, Serialize, Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub struct Hook {
122 pub id: String,
123 pub scope: HookScope,
124 pub workspace: String,
125 /// For a repository's webhook: `owner/name`.
126 pub repo: Option<String>,
127 pub url: String,
128 /// The event types it is sent, or `["*"]` for all.
129 pub events: Vec<String>,
130 pub active: bool,
131 /// The last four characters of its secret.
132 pub secret_hint: String,
133 pub created_by: String,
134 /// RFC 3339.
135 pub created_at: String,
136 /// How its latest delivery went: `delivered`, `pending` or `failed`.
137 pub last_status: Option<String>,
138 pub last_delivered_at: Option<String>,
139}
140
141/// One event sent, or being sent, to a webhook.
142#[derive(Clone, Debug, Serialize, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct HookDelivery {
145 pub id: String,
146 pub hook_id: String,
147 /// The event's id, or empty for a ping.
148 pub event_id: String,
149 pub event: String,
150 /// `pending` while it will be tried again, `delivered`, or `failed` once
151 /// it has been tried as often as it will be.
152 pub status: String,
153 pub attempts: u32,
154 /// The receiver's HTTP status, the last time it answered.
155 pub response_status: Option<u16>,
156 /// The start of what it answered.
157 pub response_body: Option<String>,
158 /// Why the last attempt failed, when the receiver could not be reached.
159 pub error: Option<String>,
160 pub duration_ms: Option<u32>,
161 /// The JSON that was sent.
162 pub payload: String,
163 /// RFC 3339.
164 pub created_at: String,
165 pub delivered_at: Option<String>,
166 pub next_attempt_at: Option<String>,
167}
168
169/// Which webhooks a call is about: a repository's, or with `repo` left out,
170/// the workspace's own.
171#[derive(Clone, Debug, Serialize, Deserialize)]
172pub struct HookOwner {
173 pub workspace: String,
174 #[serde(default)]
175 pub repo: Option<RepoPath>,
176}
177
178/// `list`. Returns `Outcome<Vec<Hook>>`. Members of the workspace only.
179#[derive(Debug, Serialize, Deserialize)]
180pub struct ListArgs {
181 pub viewer: Viewer,
182 #[serde(flatten)]
183 pub owner: HookOwner,
184}
185
186/// `create`. Returns `Outcome<CreatedHook>`. Members, for a repository's
187/// webhooks; owners, for the workspace's.
188#[derive(Debug, Serialize, Deserialize)]
189pub struct CreateArgs {
190 pub actor: User,
191 #[serde(flatten)]
192 pub owner: HookOwner,
193 pub url: String,
194 /// Event types, or `["*"]` for all. All when empty.
195 #[serde(default)]
196 pub events: Vec<String>,
197 /// Made by g1t when left out.
198 #[serde(default)]
199 pub secret: Option<String>,
200}
201
202#[derive(Clone, Debug, Serialize, Deserialize)]
203pub struct CreatedHook {
204 pub hook: Hook,
205 /// The secret, when g1t made it: shown this once.
206 pub secret: Option<String>,
207}
208
209/// `update`: only the fields given change. Returns `Outcome<Hook>`.
210#[derive(Debug, Serialize, Deserialize)]
211pub struct UpdateArgs {
212 pub actor: User,
213 #[serde(flatten)]
214 pub owner: HookOwner,
215 pub id: String,
216 #[serde(default)]
217 pub url: Option<String>,
218 #[serde(default)]
219 pub events: Option<Vec<String>>,
220 #[serde(default)]
221 pub active: Option<bool>,
222}
223
224/// `delete` (returns `Outcome<bool>`) and `ping` (sends a `ping` event and
225/// returns `Outcome<HookDelivery>`).
226#[derive(Debug, Serialize, Deserialize)]
227pub struct HookArgs {
228 pub actor: User,
229 #[serde(flatten)]
230 pub owner: HookOwner,
231 pub id: String,
232}
233
234/// `deliveries`: a webhook's latest deliveries, newest first. Returns
235/// `Outcome<Vec<HookDelivery>>`.
236#[derive(Debug, Serialize, Deserialize)]
237pub struct DeliveriesArgs {
238 pub viewer: Viewer,
239 #[serde(flatten)]
240 pub owner: HookOwner,
241 pub id: String,
242}
243
244/// `redeliver`: sends a delivery's payload again, as a new delivery.
245/// Returns `Outcome<HookDelivery>`.
246#[derive(Debug, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct RedeliverArgs {
249 pub actor: User,
250 #[serde(flatten)]
251 pub owner: HookOwner,
252 pub delivery_id: String,
253}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look254
255#[cfg(test)]
256mod tests {
257 use super::EVENT_TYPES;
258
259 /// The TypeScript mirror lists the same events, in the same order.
260 #[test]
261 fn the_typescript_mirror_lists_the_same_events() {
262 let ts = include_str!("../../../packages/contracts/src/webhooks.ts");
263 let list = ts
264 .split_once("export const EVENT_TYPES = [")
265 .and_then(|(_, rest)| rest.split_once("] as const"))
266 .map(|(list, _)| list)
267 .expect("EVENT_TYPES in webhooks.ts");
268 let mirrored: Vec<&str> = list
269 .split(',')
270 .map(|item| item.trim().trim_matches('"'))
271 .filter(|item| !item.is_empty())
272 .collect();
273 assert_eq!(mirrored, EVENT_TYPES);
274 }
275}

This file's history is long; its oldest lines are credited to the oldest commit read.