Receive real-time notifications for payout lifecycle events.
Register a webhook to stay informed about payout status changes instead of polling.
POST /webhook — subscribe a URL to events.GET /webhook — list your subscriptions.DELETE /webhook/{id} — remove one.Webhooks live in the environment they were created in: a webhook registered via the sandbox API only fires for sandbox events, and one registered via the production API only fires for production events.
When events occur, webhooks receive HTTP POST requests with a JSON payload that matches the payout schema returned by GET /payout/{id}.
Optional/nullable fields are omitted from the JSON payload entirely when they are not present (they are not sent as null). Check for the absence of a property rather than checking if it equals null.
Webhooks fire for the payout lifecycle events:
created)approved)requested)completed)deleted)expired)Method: POST, Content-Type: application/json.
Request headers:
X-Talentir-Signature: HMAC-SHA256 signature for payload verificationX-Talentir-Timestamp: Unix timestamp (seconds) when the webhook was sentRetry behavior:
Every webhook request includes a cryptographic signature. Always verify webhook signatures to ensure requests are genuinely from Talentir.
{timestamp}.{raw_request_body}.X-Talentir-Signature using timing-safe comparison.import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
timestamp: string,
signingSecret: string
): boolean {
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = createHmac('sha256', signingSecret)
.update(signedPayload)
.digest('hex');
// Timing-safe comparison to prevent timing attacks
const sigBuffer = Buffer.from(signature);
const expectedBuffer = Buffer.from(expectedSignature);
if (sigBuffer.length !== expectedBuffer.length) return false;
return timingSafeEqual(sigBuffer, expectedBuffer);
}
// In your webhook handler:
app.post('/webhook', (req, res) => {
const signature = req.headers['x-talentir-signature'];
const timestamp = req.headers['x-talentir-timestamp'];
const payload = JSON.stringify(req.body);
// Reject requests older than 5 minutes
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return res.status(400).send('Timestamp too old');
}
if (!verifyWebhookSignature(payload, signature, timestamp, YOUR_SIGNING_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook...
});