Webhooks let ScreenScore AI push event notifications to your server in real time, so you don’t have to poll the API to detect when an analysis finishes. When a subscribed event occurs, ScreenScore AI sends an HTTP POST request to your registered endpoint with a JSON payload describing the event.
Register a webhook endpoint
Open Webhooks settings
In the dashboard, navigate to Settings → Notifications → Webhooks.
Add your endpoint
Click Add Endpoint and enter the public HTTPS URL where you want to receive events. The URL must be reachable from the internet — localhost URLs are not supported.
Copy your webhook secret
After saving, the dashboard displays your webhook secret once. Copy it immediately and store it securely. You need this secret to verify that incoming requests are genuine. If you lose it, you must rotate to a new secret.
Respond to every webhook request with a 200 status code as quickly as possible, then process the payload asynchronously. If your handler takes longer than 10 seconds to respond, ScreenScore AI treats the delivery as failed and schedules a retry.
Supported events
| Event | Triggered when |
|---|
analysis.completed | An analysis finishes scoring successfully and results are available. |
analysis.failed | An analysis fails due to an error (e.g., unreachable image URL). |
batch.completed | All analyses in a batch submission have reached a terminal state. |
Every webhook payload shares the same top-level structure:
| Field | Type | Description |
|---|
event | string | The event type (e.g., analysis.completed). |
created_at | string | ISO 8601 timestamp of when the event was generated. |
data | object | The resource associated with the event (analysis, batch, etc.). |
Example: analysis.completed payload
{
"event": "analysis.completed",
"created_at": "2026-04-16T10:23:52Z",
"data": {
"id": "ana_09jz7p3nqwec1x5vbr4mt8ks2d",
"project_id": "proj_01hx4k2m9fvbzq3rcw7ndj6e5t",
"status": "completed",
"image_url": "https://example.com/screens/homepage-v2.png",
"label": "Homepage - Variant B",
"score": 78,
"breakdown": {
"visual_clarity": 82,
"visual_hierarchy": 74,
"color_contrast": 91,
"cta_effectiveness": 65,
"brand_consistency": 80
},
"feedback": [
"The primary CTA button lacks sufficient contrast with the background — consider increasing its size or switching to a higher-contrast color.",
"Text hierarchy between the headline and subheading is too similar in weight; differentiate them more clearly to guide the user's eye."
],
"error_message": null,
"created_at": "2026-04-16T10:23:45Z",
"completed_at": "2026-04-16T10:23:52Z"
}
}
Signature verification
Every webhook request includes an X-ScreenScore-Signature header containing an HMAC-SHA256 signature of the raw request body, signed with your webhook secret. Always verify this signature before processing the payload to ensure the request genuinely came from ScreenScore AI.
Never skip signature verification in production. Processing unverified payloads exposes your system to spoofed events from malicious third parties.
The verification process:
- Read the raw request body as bytes (before any JSON parsing).
- Compute HMAC-SHA256 of the raw body using your webhook secret as the key.
- Compare your computed signature to the value in
X-ScreenScore-Signature using a constant-time comparison function.
- Reject the request if the signatures do not match.
Verification code examples
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
// Use timingSafeEqual to prevent timing attacks
const expectedBuffer = Buffer.from(expected, 'hex');
const signatureBuffer = Buffer.from(signature, 'hex');
if (expectedBuffer.length !== signatureBuffer.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuffer, signatureBuffer);
}
// Express.js example
app.post('/webhooks/screenscore', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-screenscore-signature'];
const secret = process.env.SCREENSCORE_WEBHOOK_SECRET;
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Process the event asynchronously...
res.sendStatus(200);
});
Retries
If your endpoint returns a non-2xx status code or does not respond within 10 seconds, ScreenScore AI marks the delivery as failed and retries automatically:
| Attempt | Delay after previous attempt |
|---|
| 1st retry | ~30 seconds |
| 2nd retry | ~5 minutes |
| 3rd retry | ~30 minutes |
After 3 failed retries, the delivery is abandoned. You can view delivery history and manually replay failed events from Settings → Notifications → Webhooks.