Callback functions allow you to process scan results in real-time as they are discovered by the Nuclei engine.
Overview
Callbacks are invoked for each vulnerability or finding detected during a scan. They provide a way to:
Process results in real-time
Store results in custom formats
Send alerts or notifications
Implement custom filtering logic
Aggregate or analyze findings
Callback signature
type ResultCallback func ( event * output . ResultEvent )
The callback receives a single parameter: a pointer to output.ResultEvent containing all details about the finding.
ResultEvent structure
The ResultEvent contains comprehensive information about each finding:
type ResultEvent struct {
// Template information
TemplateID string
TemplatePath string
Info Info
// Target information
Host string
Matched string
MatchedAt string
// Request/Response data
Type string
Request string
Response string
ExtractedResults [] string
Metadata map [ string ] interface {}
// Timestamps
Timestamp time . Time
// Additional fields
IP string
CURLCommand string
}
Key fields
Unique identifier of the template that matched
File path of the template
Template metadata including name, severity, tags, description, etc.
Target host where the finding was discovered
The exact matched content or pattern
The location or URL where the match occurred
Protocol type (http, dns, tcp, etc.)
The raw request that was sent
The raw response that was received
Data extracted by template extractors
Additional metadata from the template
When the finding was discovered
Resolved IP address of the target
CURL command to reproduce the request
Info structure
The Info field contains template metadata:
type Info struct {
Name string
Author [] string
Severity severity . Severity
Description string
Reference [] string
Tags [] string
Metadata map [ string ] interface {}
}
Using callbacks
Single callback
Pass a single callback function to process all results:
import " github.com/projectdiscovery/nuclei/v3/pkg/output "
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
fmt . Printf ( "[ %s ] %s found on %s \n " ,
event . Info . Severity ,
event . TemplateID ,
event . Host ,
)
})
Multiple callbacks
You can pass multiple callback functions:
callback1 := func ( event * output . ResultEvent ) {
// Log to console
fmt . Printf ( "Found: %s \n " , event . TemplateID )
}
callback2 := func ( event * output . ResultEvent ) {
// Save to database
saveToDatabase ( event )
}
err := ne . ExecuteWithCallback ( callback1 , callback2 )
No callback (default output)
Passing nil uses the default JSON output to stdout:
err := ne . ExecuteWithCallback ( nil )
Callback examples
Simple console output
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
fmt . Printf ( "[ %s ] %s \n " , event . TemplateID , event . Host )
})
Detailed output with severity
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
fmt . Printf ( "[ %s ] [ %s ] %s found on %s \n " ,
time . Now (). Format ( "15:04:05" ),
event . Info . Severity ,
event . Info . Name ,
event . Host ,
)
})
Filter by severity
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
if event . Info . Severity == "critical" || event . Info . Severity == "high" {
fmt . Printf ( "HIGH PRIORITY: %s on %s \n " , event . TemplateID , event . Host )
sendAlert ( event )
}
})
Collect all results
var results [] * output . ResultEvent
var mu sync . Mutex
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
mu . Lock ()
defer mu . Unlock ()
results = append ( results , event )
})
fmt . Printf ( "Total findings: %d \n " , len ( results ))
Save to JSON file
import (
" encoding/json "
" os "
)
file , _ := os . Create ( "results.json" )
defer file . Close ()
encoder := json . NewEncoder ( file )
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
encoder . Encode ( event )
})
Send webhook notifications
import (
" bytes "
" encoding/json "
" net/http "
)
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
if event . Info . Severity == "critical" {
data , _ := json . Marshal ( event )
http . Post (
"https://webhook.example.com/alerts" ,
"application/json" ,
bytes . NewBuffer ( data ),
)
}
})
Group by severity
resultsBySeverity := make ( map [ string ] int )
var mu sync . Mutex
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
mu . Lock ()
defer mu . Unlock ()
sev := string ( event . Info . Severity )
resultsBySeverity [ sev ] ++
})
for severity , count := range resultsBySeverity {
fmt . Printf ( " %s : %d findings \n " , severity , count )
}
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
fmt . Printf ( `
=== Finding ===
Template: %s ( %s )
Severity: %s
Target: %s
Matched: %s
Time: %s
` ,
event . Info . Name ,
event . TemplateID ,
event . Info . Severity ,
event . Host ,
event . Matched ,
event . Timestamp . Format ( time . RFC3339 ),
)
})
Store in database
import " database/sql "
db , _ := sql . Open ( "postgres" , connStr )
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
_ , err := db . Exec ( `
INSERT INTO vulnerabilities (template_id, host, severity, timestamp)
VALUES ($1, $2, $3, $4)
` ,
event . TemplateID ,
event . Host ,
event . Info . Severity ,
event . Timestamp ,
)
if err != nil {
log . Printf ( "DB error: %v " , err )
}
})
Real-time dashboard updates
import " github.com/gorilla/websocket "
var wsConn * websocket . Conn // WebSocket connection to dashboard
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
data , _ := json . Marshal ( map [ string ] interface {}{
"type" : "finding" ,
"template" : event . TemplateID ,
"host" : event . Host ,
"severity" : event . Info . Severity ,
})
wsConn . WriteMessage ( websocket . TextMessage , data )
})
Thread-safe callbacks
For ThreadSafeNucleiEngine, use GlobalResultCallback:
ne , _ := nuclei . NewThreadSafeNucleiEngine ()
ne . GlobalResultCallback ( func ( event * output . ResultEvent ) {
// This callback will be used for all concurrent scans
fmt . Printf ( "[ %s ] %s \n " , event . TemplateID , event . Host )
})
// Run multiple scans
go ne . ExecuteNucleiWithOpts ([] string { "target1.com" }, opts ... )
go ne . ExecuteNucleiWithOpts ([] string { "target2.com" }, opts ... )
When using GlobalResultCallback with concurrent scans, ensure your callback is thread-safe. Use mutexes when accessing shared state.
Best practices
Keep callbacks fast - avoid blocking operations
Use goroutines for slow operations (database writes, API calls)
Add mutex locks when modifying shared state
Handle errors gracefully within callbacks
Log callback errors instead of panicking
Consider buffered channels for high-volume results
Common patterns
Buffered channel pattern
resultChan := make ( chan * output . ResultEvent , 100 )
// Consumer goroutine
go func () {
for event := range resultChan {
// Process results asynchronously
processResult ( event )
}
}()
// Callback pushes to channel
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
resultChan <- event
})
close ( resultChan )
Error handling pattern
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
defer func () {
if r := recover (); r != nil {
log . Printf ( "Callback panic: %v " , r )
}
}()
// Your callback logic
processEvent ( event )
})
Rate limiting pattern
import " golang.org/x/time/rate "
limiter := rate . NewLimiter ( 10 , 1 ) // 10 calls per second
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
limiter . Wait ( context . Background ())
sendToAPI ( event )
})
Templates can extract data using extractors:
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
if len ( event . ExtractedResults ) > 0 {
fmt . Printf ( "Extracted data from %s : \n " , event . Host )
for _ , extracted := range event . ExtractedResults {
fmt . Printf ( " - %s \n " , extracted )
}
}
})
Access custom metadata from templates:
err := ne . ExecuteWithCallback ( func ( event * output . ResultEvent ) {
if cve , ok := event . Metadata [ "cve-id" ].( string ); ok {
fmt . Printf ( "CVE: %s \n " , cve )
}
if cvss , ok := event . Metadata [ "cvss-score" ].( float64 ); ok {
fmt . Printf ( "CVSS: %.1f \n " , cvss )
}
})
Next steps
NucleiEngine API Explore engine methods
Options API Learn about all configuration options