Webhooks

Receive real-time notifications when submissions are scored, jobs are completed, or other events occur.

Webhook Endpoints

Create Endpoint

POST /v1/webhook_endpoints
FieldTypeRequiredDescription
urlstringyesHTTPS URL to receive events
eventsstring[]yesEvent types to subscribe to

Available events: submission.approved, submission.rejected, job.completed, job.cancelled

List Endpoints

GET /v1/webhook_endpoints

Get Endpoint

GET /v1/webhook_endpoints/{endpoint_id}

Update Endpoint

PATCH /v1/webhook_endpoints/{endpoint_id}

Delete Endpoint

DELETE /v1/webhook_endpoints/{endpoint_id}

Rotate Secret

POST /v1/webhook_endpoints/{endpoint_id}/rotate_secret

Webhook Events

List Events

GET /v1/webhook_events

Replay Event

POST /v1/webhook_events/{event_id}/replay

Re-delivers a webhook event to all subscribed endpoints.

Signature Verification

Every webhook delivery includes two headers:

  • X-FirstHand-Signature — Combined timestamp and HMAC digest
  • X-FirstHand-Event-Id — Event ID for idempotency and debugging
X-FirstHand-Signature: t=1710500000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-FirstHand-Event-Id: evt_01JQ...

Verification steps:

  1. Parse the t= (timestamp) and v1= (signature) components
  2. Construct the signed payload: {timestamp}.{raw_body}
  3. Compute HMAC-SHA256 with your webhook secret
  4. Compare using a timing-safe function
  5. Reject if timestamp is older than 5 minutes
import { verifyWebhookSignature } from '@firsthand/sdk';
 
verifyWebhookSignature(rawBody, signatureHeader, webhookSecret);
from firsthand import verify_webhook_signature
 
verify_webhook_signature(raw_body, signature_header, webhook_secret)

Retry Policy

Failed deliveries are retried with exponential backoff:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours
68 hours

After 6 failed attempts, the event is marked as failed and can be manually replayed.