Callback Format¶
ScamShield AI sends intelligence callbacks to report extracted evidence and scam analysis. Callbacks are sent on every turn from turn 1 onward, using overwrite semantics to ensure the latest intelligence is always available.
Callback URL¶
The callback URL is hardcoded in GuviCallbackService. The endpoint uses "update" (overwrite) semantics -- sending a callback for the same session replaces the previous one.
Callback Authentication¶
| Header | Value |
|---|---|
Content-Type |
application/json |
x-api-key |
The SCAMSHIELD_API_KEY value |
The same API key used for incoming requests is sent with outgoing callbacks.
Trigger Conditions¶
Callbacks are sent on every turn starting from turn 1 (CALLBACK_MIN_TURN = 1). This ensures:
- Intelligence is submitted even for 1-turn evaluations
- Each callback contains the latest cumulative evidence
- The endpoint's overwrite semantics make repeated sends safe
Per-Turn Callback (Primary)¶
Every incoming message triggers:
- Process the message (classify, generate response, extract evidence)
- Send callback with all accumulated evidence from the session
- Return the honeypot response
Delayed Callback (Secondary, via Cloud Tasks)¶
A separate mechanism schedules a delayed callback after 10 seconds of inactivity:
- Each incoming message schedules (or reschedules) a Cloud Tasks task
- If no new message arrives within 10 seconds, the task fires
- The task calls
send_delayed_callback, which sends the final intelligence report - The task is authenticated via OIDC token (see OIDC Verification)
This mechanism ensures a final callback is sent even if the per-turn callback failed on the last turn.
Callback Payload¶
{
"sessionId": "session-abc-123",
"status": "success",
"scamDetected": true,
"scamType": "KYC_BANKING",
"confidenceLevel": 0.92,
"totalMessagesExchanged": 5,
"engagementDurationSeconds": 450.5,
"engagementMetrics": {
"engagementDurationSeconds": 450.5,
"totalMessagesExchanged": 5
},
"extractedIntelligence": {
"bankAccounts": [],
"upiIds": ["sbikyc@oksbi"],
"phishingLinks": ["http://sbi-kyc.xyz/verify"],
"phoneNumbers": ["9876543210"],
"emailAddresses": ["fake.sbi@gmail.com"],
"suspiciousKeywords": ["KYC", "OTP", "account blocked", "urgent", "fee"],
"ifscCodes": [],
"cryptoWallets": [],
"aadhaarNumbers": [],
"panNumbers": [],
"amounts": ["500"],
"caseIds": [],
"policyNumbers": [],
"orderNumbers": []
},
"agentNotes": "Scam Type: KYC/Banking verification scam attempting to steal credentials\nConfidence: 92%\nPersona Used: sharma_uncle\nEngagement Duration: 5 messages\n\nEvidence Extracted:\nUPI IDs: sbikyc@oksbi\nPhishing URLs: http://sbi-kyc.xyz/verify\nPhone numbers: 9876543210\nEmails: fake.sbi@gmail.com\nKeywords: KYC, OTP, account blocked, urgent, fee\n\nStrategy: Engaged scammer using sharma_uncle persona with delay tactics\nand trust-building to maximize time wasted and intelligence extraction."
}
Payload Field Reference¶
| Field | Type | Description |
|---|---|---|
sessionId |
string |
Session identifier (echoed from the original request). |
status |
"success" |
Always "success" for callbacks. |
scamDetected |
boolean |
Whether a scam was detected. Same logic as the per-turn response. |
scamType |
string \| null |
Classified scam type (e.g., "KYC_BANKING", "DIGITAL_ARREST"). |
confidenceLevel |
float \| null |
Detection confidence (0.0 to 1.0). |
totalMessagesExchanged |
int |
Total messages exchanged in the session. |
engagementDurationSeconds |
float \| null |
Seconds since the session started. |
engagementMetrics |
object |
Nested engagement data (mirrors top-level fields). |
extractedIntelligence |
object |
All 14 evidence fields (cumulative across all turns). See extractedIntelligence. |
agentNotes |
string |
Human-readable summary of the analysis, scam type, evidence, and strategy. |
Cloud Tasks Scheduling¶
The delayed callback uses Google Cloud Tasks to schedule execution after a period of inactivity.
How It Works¶
Turn 1: Message arrives
→ Process message, send per-turn callback
→ Schedule Cloud Tasks task (fires in 10s)
Turn 2: Message arrives (within 10s)
→ Cancel previous task
→ Process message, send per-turn callback
→ Schedule new Cloud Tasks task (fires in 10s)
... no more messages ...
10 seconds later:
→ Cloud Tasks fires send_delayed_callback
→ Final intelligence report sent with all accumulated evidence
Task Configuration¶
| Parameter | Value |
|---|---|
| Queue | callback-queue |
| Region | asia-south1 |
| Delay | 10 seconds after the last message |
| Auth | OIDC token (service account) |
| Target | https://asia-south1-your-gcp-project-id.cloudfunctions.net/send_delayed_callback |
Task Payload¶
The send_delayed_callback function reads the full session state from Firestore and constructs the callback payload.
OIDC Token Verification¶
The delayed callback endpoint (send_delayed_callback) verifies that requests originate from Cloud Tasks, not arbitrary callers.
How It Works¶
- Cloud Tasks adds an
Authorization: Bearer <token>header to the request - The token is an OIDC JWT signed by Google, with the service account email as the
emailclaim verify_cloud_tasks_token()validates the token using Google's public keys- It checks that the
emailclaim matchesyour-gcp-project-id@appspot.gserviceaccount.com
Verification Flow¶
# In send_delayed_callback:
from utils.oidc import verify_cloud_tasks_token
ok, err_msg = verify_cloud_tasks_token(request)
if not ok:
return Response({"error": err_msg}, status=403)
Dev Mode¶
In local development (where K_SERVICE environment variable is not set), OIDC verification is skipped:
if not os.environ.get("K_SERVICE"):
logger.debug("Skipping OIDC verification (local dev)")
return True, ""
Retry and Circuit Breaker¶
The callback service includes resilience patterns:
Retry¶
- Max retries: 1 attempt per turn (kept low because callbacks are sent every turn)
- Backoff: Exponential (2^attempt seconds)
- Failed attempts are effectively retried on the next turn with fresher data
Circuit Breaker¶
- Failure threshold: 3 consecutive failures
- Reset timeout: 60 seconds
- When the circuit is open, callbacks are skipped (logged as error) until the timeout expires
- This prevents cascading failures if the callback endpoint is down