Custom Webhooks

DevGuard can send an HTTP POST request to a URL of your choice whenever a security-relevant event occurs — a new SBOM is generated, or new dependency or first-party vulnerabilities are found. Use custom webhooks to forward these events to your own custom endpoint.

Scope

A webhook belongs to exactly one organization, and optionally one project within it:

  • Organization-scoped — created via the organization endpoint. Fires for every project and asset in the organization.
  • Project-scoped — created via the project endpoint. Fires only for assets within that specific project.

When an event fires for a project, DevGuard delivers it to that project's own webhooks and to every organization-scoped webhook on the parent org. There is no asset-level scope — you can't register a webhook for a single asset.

Supported Event Types

TypeEnabled byFires when
sbomsbomEnabledA new SBOM is generated for an artifact, on the asset's default branch or a tag (not on other branches)
dependencyVulnerabilitiesvulnEnabledA scan finds one or more newly opened dependency vulnerabilities (SCA), on the default branch or a tag
firstPartyVulnerabilitiesvulnEnabledA scan finds first-party findings (SAST/secrets) that are new across all branches, on the default branch or a tag
testSent on demand via the test endpoint; never fires from real scan activity

There's no separate toggle for dependency vs. first-party findings — vulnEnabled controls both. Non-default branches never trigger deliveries, even if they introduce new findings.

Managing Webhooks

You can manage custom webhooks either from the DevGuard web UI or by talking to the API directly.

Webhook settings live in one of two places, depending on the scope you want:

  • Organization-scopedOrganization Settings → Webhooks. Fires for every project and asset in the organization.
  • Project-scopedProject Settings → Webhooks. Fires only for assets within that specific project.

Creating a Webhook

  1. Open the Webhooks panel (Organization Settings or Project Settings, depending on the scope you want).

Example on Organization Level: Screenshot placeholder: create webhook before

  1. Click Add Webhook.

  2. Fill in the fields:

Screenshot placeholder: create webhook fill

FieldRequiredDescription
namenoDisplay name for the webhook
descriptionnoFree-text description of the webhook's purpose
urlyesThe endpoint DevGuard sends POST requests to
secretnoShared secret DevGuard echoes back on every delivery in the X-Webhook-Secret header, so your endpoint can verify the sender. Encrypted at rest; never shown back to you after creation.
SBOM eventsnoDeliver sbom events
Vuln eventsnoDeliver dependencyVulnerabilities and firstPartyVulnerabilities events
  1. Click Save.

Screenshot placeholder: create webhook after

Updating a Webhook

  1. In the Webhooks panel, open the webhook you want to change.

Example on Organization Level: Screenshot placeholder: edit webhook before

  1. Edit any field — name, description, URL, secret, or which event types are enabled.

Screenshot placeholder: edit webhook confirm

  1. Click Save.

Screenshot placeholder: edit webhook after

Deleting a Webhook

  1. In the Webhooks panel, find the webhook.

Example on Organization Level: Screenshot placeholder: delete webhook before

  1. Choose Delete

Screenshot placeholder: delete webhook before

  1. Confirm the deletion.

Screenshot placeholder: delete webhook after

This immediately stops all future deliveries to that webhook.

Sending a Test Webhook

Send a real delivery to any URL, whether or not you've already saved a webhook:

  1. In the Webhooks panel, click Send Test.

Screenshot placeholder: create webhook form

Screenshot placeholder: create webhook form

  1. Choose a Payload Type to control the shape of the sample event:
ValueWire type sentDescription
emptytest{"message": "This is a test webhook from DevGuard", "timestamp": "<RFC3339>"} (the default)
sampleSbomsbomA sample CycloneDX 1.4 SBOM for a fictional example-web-app — real sbom deliveries are CycloneDX 1.6, see below
sampleDependencyVulnsdependencyVulnerabilitiesA sample Log4Shell finding (CVE-2021-44228)
sampleFirstPartyVulnsfirstPartyVulnerabilitiesA sample SQL-injection finding

Note that for the three sample types, the type field in the delivered envelope is the real event type (e.g. sbom), not test — only the default empty payload produces the literal test type.

  1. Click Send. The UI reports whether the delivery to url succeeded or failed (In our example it fails, due to an invalid URL).

Screenshot placeholder: create webhook form

Delivery Payload Format

Every delivery payload sent by DevGuard is an HTTP POST with Content-Type: application/json and this envelope:

FieldDescription
typeThe event type — one of the four values in Supported Event Types
organizationThe org the event belongs to
projectThe project the event belongs to
assetThe asset the event belongs to
assetVersionThe branch or tag the event was detected on
artifactThe scanned artifact (e.g. container image or build output). Always an empty object ({"artifactName": ""}) for firstPartyVulnerabilities, since first-party findings aren't scoped to a scan artifact.
payloadThe event-specific data — shape depends on type, detailed below

organization object

FieldTypeDescription
iduuidOrganization ID
namestringOrganization name
slugstringURL slug
descriptionstringOrganization description
isPublicbooleanWhether the org is publicly visible
languagestringOrg's configured language
contactPhoneNumberstring | nullContact phone number, if set
numberOfEmployeesnumber | nullCompany size, if set
countrystring | nullCountry, if set
industrystring | nullIndustry, if set
criticalInfrastructurebooleanWhether the org is flagged as critical infrastructure
iso27001booleanISO 27001 compliance flag
nistbooleanNIST compliance flag
grundschutzbooleanBSI IT-Grundschutz compliance flag
externalEntityProviderIdstring | omittedPresent only for orgs synced from an external provider

project object

FieldTypeDescription
iduuidProject ID
namestringProject name
slugstringURL slug
descriptionstringProject description
isPublicbooleanWhether the project is publicly visible
typestringProject type (e.g. application)
parentIduuid | omittedParent project ID, for sub-projects
parentobject | omittedReserved for a nested parent object; currently always omitted/null in deliveries
repositoryIdstring | nullLinked repository ID
repositoryNamestring | nullLinked repository name
externalEntityProviderId / externalEntityIdstring | omittedPresent only for projects synced from an external provider

asset object

FieldTypeDescription
iduuidAsset ID
namestringAsset name
slugstringURL slug
descriptionstringAsset description
projectIduuidParent project ID
availabilityRequirement / integrityRequirement / confidentialityRequirementstringConfigured CIA requirement level (e.g. low, medium, high)
reachableFromInternetbooleanWhether the asset is marked as internet-reachable
repositoryId / repositoryNamestring | nullLinked repository
signingPubKeystring | nullAsset's configured signing public key
cvssAutomaticTicketThreshold / riskAutomaticTicketThresholdnumber | nullAuto-ticketing thresholds, if configured
externalEntityProviderId / externalEntityIdstring | omittedPresent only for externally-synced assets

assetVersion object

FieldTypeDescription
namestringBranch or tag name
assetIduuidParent asset ID
slugstringURL slug
defaultBranchbooleanWhether this is the asset's default branch
typestringbranch or tag
signingPubKeystring | nullVersion-specific signing key override, if set
metadataobjectArbitrary key/value metadata attached to this version

artifact object

FieldTypeDescription
artifactNamestringName of the scanned artifact (e.g. my-image:latest). Empty string for firstPartyVulnerabilities.

payload for sbom

The full CycloneDX 1.6 BOM for the artifact, as generated by DevGuard — components, licenses, and metadata:

payload for dependencyVulnerabilities

An array of dependency vulnerability objects:

FieldTypeDescription
iduuidVulnerability ID
messagestring | nullHuman-readable summary
assetVersionIdstringBranch/tag name (despite the key name, this is the version's name, not a UUID)
assetIdstringAsset ID
statestringOne of open, fixed, accepted, falsePositive, markedForTransfer, implemented, notApplicable
cveobject | nullFull CVE record — see below
cveIDstringCVE identifier
componentPurlstringAffected component, as a package URL
componentFixedVersionstring | nullVersion that fixes this component, if known
vulnerabilityPathstring[]Dependency chain from the direct dependency to the vulnerable component
directDependencyFixedVersionstring | nullFixed version of the direct (top-level) dependency, if different from the component itself
effortnumber | nullEstimated remediation effort
riskAssessmentnumber | nullDevGuard's calculated risk score (0–100)
rawRiskAssessmentnumber | nullUnderlying raw risk value before normalization
prioritynumber | nullRemediation priority ranking
lastDetectedtimestampWhen this vulnerability was last seen in a scan
createdAttimestampWhen this vulnerability was first detected
riskRecalculatedAttimestampWhen the risk score was last recalculated
ticketId / ticketUrlstring | nullLinked ticket, if one was created
manualTicketCreationbooleanWhether ticket creation was triggered manually rather than automatically
artifactsarrayArtifacts this finding appears in — each { artifactName, assetVersionName, assetId }
exploitsarrayKnown exploits for this specific finding — see ExploitDTO below

cve object:

FieldTypeDescription
cvestringCVE identifier
descriptionstringVulnerability description
cvssnumberBase CVSS score
vectorstringCVSS vector string
referencesstringReference links
datePublished / dateLastModifiedtimestampNVD publish/update dates
cisaExploitAdd / cisaActionDuedate | nullCISA KEV catalog dates, if listed
cisaRequiredAction / cisaVulnerabilityNamestring | nullCISA KEV metadata, if listed
epssnumber | nullEPSS exploitation probability score
percentilenumber | nullEPSS percentile
riskobject{ baseScore, withEnvironment, withThreatIntelligence, withEnvironmentAndThreatIntelligence } — DevGuard's layered risk calculation
exploitsarrayKnown public exploits for this CVE in general (see below)
relationshipsarrayRelated CVEs, each { relationshipType, targetCve }

Example:

payload for firstPartyVulnerabilities

An array of first-party (SAST/secrets) finding objects:

FieldTypeDescription
iduuidFinding ID
scannerIdsstringScanner(s) that reported this finding (e.g. semgrep)
messagestring | nullHuman-readable summary
assetVersionNamestringBranch/tag name
assetIdstringAsset ID
statestringSame VulnState values as dependency vulnerabilities
ruleIdstringScanner rule identifier
ruleName / ruleHelp / ruleHelpURI / ruleDescriptionstringHuman-readable rule metadata
rulePropertiesobjectArbitrary scanner-provided rule metadata (e.g. {"severity": "HIGH", "cwe": "CWE-89"})
uristringFile path where the finding was detected
snippetContentsarrayCode snippets — each { startLine, endLine, startColumn, endColumn, snippet }
commitstringCommit SHA the finding was detected on
author / email / datestringCommit author metadata
createdAttimestampWhen the finding was first detected
ticketId / ticketUrlstring | nullLinked ticket, if one was created
manualTicketCreationbooleanWhether ticket creation was triggered manually

Example:

payload for test

Only when payloadType is empty (the default):

Verifying Webhook Deliveries with Webhook-Secrets

If you configured a secret, every delivery includes it verbatim in a header:

X-Webhook-Secret: <your-secret>

Example (Node.js / Express):

Retry Behavior & Delivery Constraints

DevGuard attempts each delivery up to 3 times total:

  1. Attempt 1 fires immediately. Like every attempt, it can take up to 30 seconds to time out before being counted as failed.
  2. If attempt 1 failed with a retryable error, DevGuard waits 1 second, then fires attempt 2 (also capped at 30 seconds).
  3. If attempt 2 also failed, DevGuard waits 5 seconds, then fires attempt 3 — the last one. If this fails too, DevGuard gives up.

Retries only happen on 5xx responses, 408, 429, and network-level errors (timeouts, connection failures). Any other 4xx response (e.g. 401, 404) is treated as permanent and is not retried — DevGuard gives up after attempt 1.

Two more constraints apply regardless of retries:

  • Outbound requests are rate-limited to 10 requests/second (burst 20) per destination host.
  • DevGuard refuses to deliver to localhost or any IP that resolves to a loopback address or the cloud-metadata address 169.254.169.254 — you can't point a webhook at an address on the DevGuard host itself.

Have feedback? We want to hear from you!

Fields marked with * are required