Skip to main content

Webhooks

Webhooks send real-time POST requests to your URL when events happen in your Flowxtra account — new applications, stage changes, hires, job updates, and meetings.

Setup

  1. Go to Plugins → Webhooks in your dashboard
  2. Click Add Webhook
  3. Enter a name, your endpoint URL, and select events
  4. Flowxtra generates an HMAC secret — copy it immediately (shown only once)

Endpoints

All webhook management endpoints require authentication and are tenant-scoped.

MethodEndpointDescription
GET/api/plugins/webhooks/eventsList available event types
GET/api/plugins/webhooksList your webhooks
POST/api/plugins/webhooksCreate a webhook
GET/api/plugins/webhooks/{id}Get webhook details + recent logs
PUT/api/plugins/webhooks/{id}Update a webhook
DELETE/api/plugins/webhooks/{id}Delete a webhook and its logs
POST/api/plugins/webhooks/{id}/testSend a test payload
GET/api/plugins/webhooks/{id}/logsPaginated delivery logs

Create a Webhook

POST /api/plugins/webhooks?subdomain=acme

Request Body

{
"name": "My ATS Integration",
"url": "https://example.com/webhooks/flowxtra",
"events": [
"application.created",
"application.stage_changed",
"application.hired"
]
}

Response

{
"success": true,
"data": {
"id": 1,
"company_id": 1,
"name": "My ATS Integration",
"url": "https://example.com/webhooks/flowxtra",
"events": ["application.created", "application.stage_changed", "application.hired"],
"secret": "a1b2c3d4e5f6...full-64-char-secret",
"status": true,
"created_at": "2026-04-06T12:00:00.000000Z"
}
}
warning

The secret is returned in full only on creation. Store it securely — subsequent responses mask it to ...last6chars.

Events

EventTriggerPayload Fields
application.createdNew candidate appliescandidate_job_id, job_id, email, first_name, last_name, stage_id
application.stage_changedCandidate moves pipeline stagecandidate_job_id, job_id, new_stage_id, email
application.rejectedCandidate rejectedcandidate_job_id, job_id, email
application.hiredCandidate hired as team membercandidate_job_id, job_id, team_member_id, email
job.publishedJob status set to Livejob_id, title, status
job.closedJob closed or archivedjob_id, title, status
meeting.scheduledMeeting created (Zoom/Google)meet_id, candidate_job_id, scheduled_at, type

Payload Format

Every webhook delivery is a POST request with a JSON body:

{
"event": "application.created",
"timestamp": "2026-04-06T14:30:00+00:00",
"data": {
"candidate_job_id": 42,
"job_id": 7,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"stage_id": 1
}
}

Request Headers

Each delivery includes these headers:

HeaderDescriptionExample
Content-TypeAlways JSONapplication/json
X-Flowxtra-SignatureHMAC SHA-256 signaturesha256=abc123...
X-Flowxtra-EventEvent typeapplication.created
X-Flowxtra-DeliveryUnique delivery ID (UUID)550e8400-e29b-...
User-AgentIdentifies FlowxtraFlowxtra-Webhooks/1.0

Verifying Signatures

Every delivery is signed with your webhook's HMAC secret. Always verify signatures to ensure the request is from Flowxtra.

Node.js

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

// In your handler:
const isValid = verifySignature(
req.rawBody,
req.headers['x-flowxtra-signature'],
process.env.FLOWXTRA_WEBHOOK_SECRET
);

PHP

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_FLOWXTRA_SIGNATURE'];
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);

if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}

Python

import hmac, hashlib

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)

Retry Policy

If your endpoint returns a non-2xx status code or the connection fails, Flowxtra retries up to 3 times with exponential backoff:

AttemptDelay
1st retry10 seconds
2nd retry1 minute
3rd retry5 minutes

After 3 failed attempts, the delivery is marked as failed. Check Plugins → Webhooks → Logs to see delivery history and debug failures.

Best Practices

  • Respond quickly — return 200 OK within 15 seconds. Process the data asynchronously if needed.
  • Verify signatures — always check X-Flowxtra-Signature before processing.
  • Handle duplicates — use X-Flowxtra-Delivery to deduplicate if your endpoint is called more than once.
  • Use HTTPS — always use an https:// endpoint URL.
  • Monitor logs — check delivery logs regularly to catch failures early.

Test Webhook

Send a test payload to verify your endpoint is working:

POST /api/plugins/webhooks/{id}/test?subdomain=acme

This sends a webhook.test event synchronously and returns the result immediately:

{
"success": true,
"data": {
"success": true,
"status_code": 200,
"message": "Test webhook delivered successfully."
}
}

Delivery Logs

View delivery history for a webhook:

GET /api/plugins/webhooks/{id}/logs?subdomain=acme
{
"success": true,
"data": {
"data": [
{
"id": 1,
"event": "application.created",
"status_code": 200,
"success": true,
"attempt": 1,
"delivered_at": "2026-04-06T14:30:01.000000Z",
"created_at": "2026-04-06T14:30:00.000000Z"
}
],
"current_page": 1,
"total": 1
}
}