Guides

Set up webhooks

Receive real-time notifications for payout lifecycle events.

Register a webhook to stay informed about payout status changes instead of polling.

Manage subscriptions

  • 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.

Event payload

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.

Event triggers

Webhooks fire for the payout lifecycle events:

  • Payout creation (status created)
  • Payout approval (status approved)
  • Payout request (status requested)
  • Payout completion (status completed)
  • Payout deletion (status deleted)
  • Payout expiration (status expired)

Delivery behavior

Method: POST, Content-Type: application/json.

Request headers:

  • X-Talentir-Signature: HMAC-SHA256 signature for payload verification
  • X-Talentir-Timestamp: Unix timestamp (seconds) when the webhook was sent

Retry behavior:

  • Server errors (5xx): retried up to 10 times with exponential backoff (10s, 20s, 40s, 80s, 160s, 320s, 640s, 1280s, 2560s, 5120s). Total retry window is approximately 2.8 hours.
  • Rate limiting (429): retried after a 60 second delay.
  • Gone (410): the webhook subscription is automatically deleted. No retry.
  • Other client errors (4xx): not retried. These indicate a permanent failure (e.g. invalid endpoint configuration).
  • Success (2xx): delivery confirmed.

Verify signatures

Every webhook request includes a cryptographic signature. Always verify webhook signatures to ensure requests are genuinely from Talentir.

  1. Extract the signature and timestamp from the request headers.
  2. Construct the signed payload: {timestamp}.{raw_request_body}.
  3. Compute HMAC-SHA256 using your webhook's signing secret.
  4. Compare the computed signature with X-Talentir-Signature using timing-safe comparison.
  5. Optionally, reject requests with timestamps older than 5 minutes to prevent replay attacks.
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...
});