Payload Reference
Every webhook request from Stringhive has the same set of headers and a JSON body.
Headers
Content-Type: application/json
User-Agent: Stringhive-Webhook/1.0
X-Stringhive-Event: string.created
X-Stringhive-Delivery: a1b2c3d4-e5f6-...
X-Stringhive-Signature: sha256=abc123...
X-Stringhive-Delivery is a unique ID for each delivery attempt. X-Stringhive-Signature is only present if you configured a secret.
Verifying the signature
If you set a secret, verify the signature before processing the payload. Stringhive uses HMAC-SHA256.
// PHP
$payload = file_get_contents('php://input');
$signature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
$trusted = hash_equals($signature, $_SERVER['HTTP_X_STRINGHIVE_SIGNATURE']);
// Node.js
const crypto = require('crypto');
const signature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
const trusted = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(req.headers['x-stringhive-signature'])
);
Always use a timing-safe comparison. Regular string comparison is vulnerable to timing attacks.
Payload: string.created
{
"event": "string.created",
"hive": { "slug": "my-app", "name": "My App" },
"string": {
"key": "nav.home",
"source_value": "Home",
"file": "nav.php",
"is_plural": false
},
"user": { "id": "...", "name": "Daniel" },
"timestamp": "2025-06-01T12:00:00Z"
}
Payload: string.updated
{
"event": "string.updated",
"hive": { "slug": "my-app", "name": "My App" },
"string": {
"key": "nav.home",
"source_value": "Home page",
"previous_value": "Home",
"file": "nav.php",
"is_plural": false
},
"user": { "id": "...", "name": "Daniel" },
"timestamp": "2025-06-01T12:00:00Z"
}
Payload: string.deleted
{
"event": "string.deleted",
"hive": { "slug": "my-app", "name": "My App" },
"string": { "key": "nav.old_page", "file": "nav.php" },
"user": { "id": "...", "name": "Daniel" },
"timestamp": "2025-06-01T12:00:00Z"
}
Payload: translation.updated
{
"event": "translation.updated",
"hive": { "slug": "my-app", "name": "My App" },
"string": { "key": "nav.home", "file": "nav.php" },
"translation": {
"locale": "de",
"value": "Startseite",
"state": "translated"
},
"user": { "id": "...", "name": "Maria" },
"timestamp": "2025-06-01T12:00:00Z"
}
Payload: translation.approved
{
"event": "translation.approved",
"hive": { "slug": "my-app", "name": "My App" },
"string": { "key": "nav.home", "file": "nav.php" },
"translation": {
"locale": "de",
"value": "Startseite",
"state": "approved"
},
"user": { "id": "...", "name": "Daniel" },
"timestamp": "2025-06-01T12:00:00Z"
}
Payload: comment.created
Fires when someone leaves a comment on a string. The locale field is present if the comment was left in the context of a specific locale, and null if it was a general string comment.
{
"event": "comment.created",
"hive": { "slug": "my-app", "name": "My App" },
"string": { "key": "checkout.submit", "file": "checkout.php" },
"comment": {
"body": "The German here sounds too formal, consider using du-form.",
"locale": "de"
},
"user": { "id": "...", "name": "Maria" },
"timestamp": "2025-06-01T12:00:00Z"
}
Payload: locale.completed
Fires when the last unapproved string in a Hive gets approved for a locale. Every single string is now approved. Pop the champagne.
{
"event": "locale.completed",
"hive": { "slug": "my-app", "name": "My App" },
"locale": "de",
"string_count": 142,
"timestamp": "2025-06-01T12:00:00Z"
}
This event fires once per locale per "completion". If a string is later unapproved and re-approved, it fires again.