public async Task <Dictionary <string, string> > ProcessAsync() { JObject record = JObject.Parse(this.requestBody); // Some events e.g., "member_added", does not have "repository" as part of the Webhooks payload since they are only associated with the // organization and not with a specific repository. Use defaults (0 and empty string) for repository id and repository name since they are optional. long repositoryId = Repository.NoRepositoryId; string repositoryName = Repository.NoRepositoryName; JToken repositoryIdToken = record.SelectToken("$.repository.id"); if (repositoryIdToken != null) { repositoryId = repositoryIdToken.Value <long>(); } JToken repositoryNameToken = record.SelectToken("$.repository.name"); if (repositoryNameToken != null) { repositoryName = repositoryNameToken.Value <string>(); } // There have been GitHub payloads that are impossible to process because they are missing required attributes (e.g., organization, repository.id, etc.). // Instead of failing with a cryptic Newtonsoft JSON parsing error, handle these cases more graciously and log them in telemetry for future debugging and validation. if (!repositoryName.Equals(Repository.NoRepositoryName) && repositoryId == Repository.NoRepositoryId) { // Case 1: there is a repository name but no repository id. Dictionary <string, string> properties = new Dictionary <string, string>() { { "RequestBody", this.requestBody }, { "Reason", "repository.id is missing." }, }; this.telemetryClient.TrackEvent("UnexpectedWebhookPayload", properties); return(new Dictionary <string, string>()); } JToken organizationIdToken = record.SelectToken("$.organization.id"); if (organizationIdToken == null) { // Case 2: organization login is missing. Dictionary <string, string> properties = new Dictionary <string, string>() { { "RequestBody", this.requestBody }, { "Reason", "organization.id is missing." }, }; this.telemetryClient.TrackEvent("UnexpectedWebhookPayload", properties); return(new Dictionary <string, string>()); } long organizationId = organizationIdToken.Value <long>(); JToken organizationLoginToken = record.SelectToken("$.organization.login"); if (organizationLoginToken == null) { // Case 3: organization id is missing. Dictionary <string, string> properties = new Dictionary <string, string>() { { "RequestBody", this.requestBody }, { "Reason", "organization.login is missing." }, }; this.telemetryClient.TrackEvent("UnexpectedWebhookPayload", properties); return(new Dictionary <string, string>()); } string organizationLogin = organizationLoginToken.Value <string>(); Repository repository = new Repository(organizationId, repositoryId, organizationLogin, repositoryName); foreach (IRecordWriter recordWriter in this.recordWriters) { recordWriter.SetOutputPathPrefix($"{repository.OrganizationId}/{repository.RepositoryId}"); } if (repository.IsValid()) { int eventCount = await this.eventsBookkeeper.IncrementCountAsync(repository).ConfigureAwait(false); if (eventCount >= EventCountLimit) { await this.eventsBookkeeper.SignalCountAsync(repository).ConfigureAwait(false); await this.eventsBookkeeper.ResetCountAsync(repository).ConfigureAwait(false); } } string eventType = this.context.EventType; await this.CacheRecord(record, repository, eventType).ConfigureAwait(false); RecordContext recordContext = new RecordContext() { RecordType = eventType }; foreach (IRecordWriter recordWriter in this.recordWriters) { await recordWriter.WriteRecordAsync(record, recordContext).ConfigureAwait(false); } ICollector collector = CollectorFactory.Instance.GetCollector(eventType, this.context, this.authentication, this.httpClient, this.recordWriters, this.collectorCache, this.telemetryClient, this.apiDomain); await collector.ProcessWebhookPayloadAsync(record, repository).ConfigureAwait(false); Dictionary <string, string> additionalSessionEndProperties = new Dictionary <string, string>(this.RetrieveAdditionalPrimaryKeys(record)) { { "OrganizationLogin", repository.OrganizationLogin }, { "OrganizationId", repository.OrganizationId.ToString() }, { "RepositoryName", repository.RepositoryName }, { "RepositoryId", repository.RepositoryId.ToString() }, }; return(additionalSessionEndProperties); }