TurfAITurfAI Developers
Api_syncedGuides

TurfAI Webhook Integration Guide

Synced from the source repositories. Do not edit by hand.

This guide explains how to integrate external applications with TurfAI workflows using webhooks.


Overview

Webhooks allow external applications (Google Forms, TypeForm, custom apps) to trigger TurfAI workflows automatically. When an external event occurs, a POST request to the webhook URL starts a workflow execution.

┌─────────────────┐     POST      ┌─────────────────┐     Queue      ┌─────────────────┐
│  External App   │ ────────────> │  TurfAI DMS     │ ────────────>  │  Job Router     │
│  (Form/App)     │  + payload    │  (Webhook)      │                │  (Execution)    │
└─────────────────┘               └─────────────────┘                └─────────────────┘

                                         │ Returns

                                  execution_id +
                                  polling_token

Quick Start

Step 1: Create a Workflow

First, create a workflow in the TurfAI UI or via API.

Step 2: Create a Webhook

curl -X POST https://apisandbox.turfai.in/api/webhooks \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "activity": 123,
    "name": "Job Application Webhook"
  }'

Response:

{
  "success": true,
  "data": {
    "id": 1,
    "name": "Job Application Webhook",
    "url": "https://apisandbox.turfai.in/api/webhooks/trigger/1",
    "secret_key": "a1b2c3d4e5f6...64_char_hex_string...",
    "active": true,
    "trigger_count": 0
  }
}

Step 3: Configure External App

Copy the url and secret_key to your external application.

Step 4: Trigger the Workflow

curl -X POST https://apisandbox.turfai.in/api/webhooks/trigger/1 \
  -H "X-Webhook-Secret: a1b2c3d4e5f6...your_secret..." \
  -H "Content-Type: application/json" \
  -d '{
    "candidate_name": "John Doe",
    "email": "john@example.com",
    "resume_url": "https://drive.google.com/file/d/ABC123"
  }'

Response:

{
  "success": true,
  "execution_id": 456,
  "message": "Workflow execution started",
  "polling_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Step 5: Poll for Results

curl https://apisandbox.turfai.in/api/workflow-executions/456 \
  -H "Authorization: Bearer YOUR_POLLING_TOKEN"

API Reference

Base URL

EnvironmentURL
Developmenthttp://localhost:1338/api
Staginghttps://apisandbox.turfai.in/api

Create Webhook

Creates a webhook for a workflow.

Endpoint: POST /webhooks

Authentication: Required (JWT Bearer token)

Request Body:

FieldTypeRequiredDescription
activitynumberYesWorkflow/Activity ID
namestringNoHuman-readable name
event_typestringNoEvent type (e.g., "form_submission")
max_file_size_mbnumberNoMax upload size per file (1-50, default: 10)
allowed_mime_typesarrayNoAllowed file types (default: common doc types)
auto_enable_ragbooleanNoAuto-index uploaded files for RAG (default: false)
accept_filesbooleanNoEnable file uploads (default: true)

Example:

curl -X POST https://apisandbox.turfai.in/api/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "activity": 123,
    "name": "HR Form Webhook",
    "event_type": "form_submission",
    "max_file_size_mb": 25,
    "auto_enable_rag": true
  }'

Response:

{
  "success": true,
  "data": {
    "id": 1,
    "name": "HR Form Webhook",
    "url": "https://apisandbox.turfai.in/api/webhooks/trigger/1",
    "secret_key": "64_character_hex_secret",
    "active": true,
    "event_type": "form_submission",
    "trigger_count": 0,
    "createdAt": "2024-11-24T10:00:00.000Z"
  }
}

Trigger Workflow (PUBLIC)

Triggers a workflow execution via webhook. No authentication required - uses secret validation.

Endpoint: POST /webhooks/trigger/:webhookId

Authentication: None (uses X-Webhook-Secret header)

Headers:

HeaderRequiredDescription
X-Webhook-SecretYesThe webhook's secret_key
Content-TypeYesapplication/json

Request Body:

Any JSON object - becomes the workflow's inputs.

Example:

curl -X POST https://apisandbox.turfai.in/api/webhooks/trigger/1 \
  -H "X-Webhook-Secret: your_64_char_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "candidate_name": "Jane Smith",
    "email": "jane@example.com",
    "role_id": "software_engineer",
    "resume_file_id": "1ABC123xyz"
  }'

Success Response (200):

{
  "success": true,
  "execution_id": 456,
  "message": "Workflow execution started",
  "polling_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Error Responses:

StatusErrorDescription
400File upload failedFile validation error (size, type, quota)
401Missing X-Webhook-Secret headerSecret header not provided
401Invalid webhook secretSecret doesn't match
403Webhook is inactiveWebhook has been disabled
404Webhook not foundInvalid webhook ID

Trigger with File Upload

Webhooks support multipart/form-data for uploading files directly. Files are:

  • Uploaded to GCS as documents owned by the webhook creator
  • Validated against size and type limits
  • Counted against the webhook owner's storage quota

Example with Files:

curl -X POST https://apisandbox.turfai.in/api/webhooks/trigger/1 \
  -H "X-Webhook-Secret: your_64_char_secret" \
  -F "resume=@candidate_resume.pdf" \
  -F "cover_letter=@cover_letter.docx" \
  -F "applicant_name=Jane Smith" \
  -F "email=jane@example.com"

Response with Files:

{
  "success": true,
  "execution_id": 456,
  "message": "Workflow execution started",
  "polling_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "uploaded_documents": [
    {
      "field_name": "resume",
      "document_id": 789,
      "file_url": "gs://turfdms/uploads/default/42/1732444800000-candidate_resume.pdf",
      "file_name": "candidate_resume.pdf",
      "mime_type": "application/pdf",
      "file_size": 245760
    },
    {
      "field_name": "cover_letter",
      "document_id": 790,
      "file_url": "gs://turfdms/uploads/default/42/1732444800001-cover_letter.pdf",
      "file_name": "cover_letter.docx",
      "mime_type": "application/pdf",
      "file_size": 52480
    }
  ],
  "documents_count": 2
}

Accessing Files in Workflow:

The uploaded file information is injected into workflow inputs:

// Direct access by field name
inputs.resume_file_url    // "gs://turfdms/uploads/..."
inputs.resume_document_id // 789

// Structured access
inputs._documents.resume  // { field_name, document_id, file_url, ... }
inputs._uploaded_files    // Array of all uploaded files

File Upload Limits:

LimitDefaultConfigurable
Max file size10 MBPer webhook (1-50 MB)
Allowed typesPDF, images, Office docsPer webhook
Storage quotaUser's limitPer user

Supported File Types (default):

  • application/pdf
  • image/jpeg, image/png, image/gif, image/webp
  • text/plain, text/csv, application/json
  • application/msword (doc)
  • application/vnd.openxmlformats-officedocument.wordprocessingml.document (docx)
  • application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

DOCX Auto-Conversion:

DOCX files are automatically converted to PDF for better processing compatibility.


Poll Execution Status

Check the status of a workflow execution.

Endpoint: GET /workflow-executions/:executionId

Authentication: Required (JWT or polling_token)

Response:

{
  "data": {
    "id": 456,
    "status": "completed",
    "inputs": { "candidate_name": "Jane Smith", ... },
    "results": {
      "classification": { "type": "resume", "confidence": 0.95 },
      "extraction": { "name": "Jane Smith", "skills": [...] }
    },
    "started_at": "2024-11-24T10:00:00.000Z",
    "completed_at": "2024-11-24T10:00:45.000Z"
  }
}

Status Values:

StatusDescription
queuedWaiting to be processed
runningCurrently executing
completedFinished successfully
failedExecution failed (check error field)

List Webhooks

List all webhooks owned by the authenticated user.

Endpoint: GET /webhooks

Authentication: Required

Response:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "HR Form Webhook",
      "url": "https://apisandbox.turfai.in/api/webhooks/trigger/1",
      "active": true,
      "trigger_count": 42,
      "last_triggered_at": "2024-11-24T09:30:00.000Z",
      "activity": { "id": 123, "name": "Job Application Processing" }
    }
  ]
}

Update Webhook

Update webhook settings.

Endpoint: PUT /webhooks/:id

Authentication: Required (must own webhook)

Request Body:

FieldTypeDescription
namestringNew name
activebooleanEnable/disable webhook
event_typestringEvent type

Example:

curl -X PUT https://apisandbox.turfai.in/api/webhooks/1 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "active": false }'

Delete Webhook

Permanently delete a webhook.

Endpoint: DELETE /webhooks/:id

Authentication: Required (must own webhook)

curl -X DELETE https://apisandbox.turfai.in/api/webhooks/1 \
  -H "Authorization: Bearer $TOKEN"

Regenerate Secret

Generate a new secret key (invalidates old secret immediately).

Endpoint: POST /webhooks/:id/regenerate-secret

Authentication: Required (must own webhook)

curl -X POST https://apisandbox.turfai.in/api/webhooks/1/regenerate-secret \
  -H "Authorization: Bearer $TOKEN"

Response:

{
  "success": true,
  "data": {
    "id": 1,
    "secret_key": "new_64_character_hex_secret"
  }
}

Integration Examples

Google Forms

  1. Create a Google Form
  2. Go to Extensions > Apps Script
  3. Add this code:
function onFormSubmit(e) {
  const WEBHOOK_URL = 'https://apisandbox.turfai.in/api/webhooks/trigger/YOUR_ID';
  const WEBHOOK_SECRET = 'your_secret_key';

  const formResponse = e.response;
  const itemResponses = formResponse.getItemResponses();

  // Build payload from form responses
  const payload = {};
  itemResponses.forEach(item => {
    payload[item.getItem().getTitle()] = item.getResponse();
  });

  // Add metadata
  payload.submitted_at = new Date().toISOString();
  payload.form_id = e.source.getId();

  // Send to TurfAI
  const options = {
    method: 'post',
    headers: {
      'Content-Type': 'application/json',
      'X-Webhook-Secret': WEBHOOK_SECRET
    },
    payload: JSON.stringify(payload)
  };

  try {
    const response = UrlFetchApp.fetch(WEBHOOK_URL, options);
    console.log('Webhook response:', response.getContentText());
  } catch (error) {
    console.error('Webhook failed:', error);
  }
}
  1. Set up trigger: Triggers > Add Trigger > onFormSubmit > From form > On form submit

TypeForm

  1. Go to Connect > Webhooks
  2. Add webhook URL: https://apisandbox.turfai.in/api/webhooks/trigger/YOUR_ID
  3. Use a middleware service (Zapier/n8n) to add the X-Webhook-Secret header

Or use TypeForm's API with a custom integration:

// Using n8n or custom middleware
const payload = {
  ...typeformData.form_response.answers,
  submitted_at: typeformData.form_response.submitted_at
};

fetch(WEBHOOK_URL, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Webhook-Secret': WEBHOOK_SECRET
  },
  body: JSON.stringify(payload)
});

Node.js Application

const axios = require('axios');

async function triggerWorkflow(formData) {
  const WEBHOOK_URL = process.env.TURFAI_WEBHOOK_URL;
  const WEBHOOK_SECRET = process.env.TURFAI_WEBHOOK_SECRET;

  try {
    const response = await axios.post(WEBHOOK_URL, formData, {
      headers: {
        'Content-Type': 'application/json',
        'X-Webhook-Secret': WEBHOOK_SECRET
      }
    });

    console.log('Execution started:', response.data.execution_id);

    // Optional: Poll for results
    return await pollExecution(response.data.execution_id, response.data.polling_token);

  } catch (error) {
    console.error('Webhook failed:', error.response?.data || error.message);
    throw error;
  }
}

async function pollExecution(executionId, pollingToken, maxAttempts = 30) {
  const BASE_URL = 'https://apisandbox.turfai.in/api';

  for (let i = 0; i < maxAttempts; i++) {
    const response = await axios.get(`${BASE_URL}/workflow-executions/${executionId}`, {
      headers: { 'Authorization': `Bearer ${pollingToken}` }
    });

    const { status, results, error } = response.data.data;

    if (status === 'completed') {
      return results;
    }

    if (status === 'failed') {
      throw new Error(`Workflow failed: ${error}`);
    }

    // Wait 2 seconds before next poll
    await new Promise(r => setTimeout(r, 2000));
  }

  throw new Error('Polling timeout');
}

Python Application

import requests
import time
import os

WEBHOOK_URL = os.getenv('TURFAI_WEBHOOK_URL')
WEBHOOK_SECRET = os.getenv('TURFAI_WEBHOOK_SECRET')
BASE_URL = 'https://apisandbox.turfai.in/api'

def trigger_workflow(form_data: dict) -> dict:
    """Trigger a TurfAI workflow via webhook."""
    response = requests.post(
        WEBHOOK_URL,
        json=form_data,
        headers={
            'Content-Type': 'application/json',
            'X-Webhook-Secret': WEBHOOK_SECRET
        }
    )
    response.raise_for_status()
    return response.json()

def poll_execution(execution_id: int, polling_token: str, max_attempts: int = 30) -> dict:
    """Poll for workflow execution results."""
    for _ in range(max_attempts):
        response = requests.get(
            f'{BASE_URL}/workflow-executions/{execution_id}',
            headers={'Authorization': f'Bearer {polling_token}'}
        )
        response.raise_for_status()

        data = response.json()['data']
        status = data.get('status')

        if status == 'completed':
            return data.get('results')

        if status == 'failed':
            raise Exception(f"Workflow failed: {data.get('error')}")

        time.sleep(2)

    raise Exception('Polling timeout')

# Usage
result = trigger_workflow({
    'candidate_name': 'John Doe',
    'email': 'john@example.com',
    'resume_file_id': '1ABC123xyz'
})

execution_id = result['execution_id']
polling_token = result['polling_token']

results = poll_execution(execution_id, polling_token)
print('Workflow results:', results)

Security

Secret Validation

  • Secrets are 64-character hex strings (256 bits of entropy)
  • Validation uses timing-safe comparison to prevent timing attacks
  • Always store secrets securely (environment variables, secret managers)

Best Practices

  1. Never expose secrets in client-side code
  2. Use HTTPS for all webhook calls
  3. Rotate secrets periodically using the regenerate endpoint
  4. Disable unused webhooks rather than deleting them
  5. Monitor trigger_count for unusual activity

Polling Token

  • The polling_token returned by trigger is a short-lived JWT (1 hour)
  • Use it only for polling execution status
  • Don't store it long-term

Error Handling

HTTP Status Codes

CodeMeaning
200Success
400Bad request (invalid payload)
401Unauthorized (missing/invalid secret)
403Forbidden (webhook inactive)
404Not found (invalid webhook ID)
500Server error

Retry Strategy

async function triggerWithRetry(payload, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await triggerWorkflow(payload);
    } catch (error) {
      if (error.response?.status >= 500 && attempt < maxRetries) {
        // Server error - retry with exponential backoff
        await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
        continue;
      }
      throw error; // Don't retry 4xx errors
    }
  }
}

Rate Limits

LimitValue
Requests per webhook100/minute
Concurrent executions10 per user
Payload size1 MB
Polling requests60/minute

Troubleshooting

Webhook Not Triggering

  1. Check webhook is active: true
  2. Verify secret matches exactly (case-sensitive)
  3. Ensure Content-Type: application/json header is set
  4. Check DMS logs: gcloud run logs read turfai-dms --limit=50

Execution Stuck in "queued"

  1. Verify Job Router is running
  2. Check Redis connection
  3. Look for errors in processor logs

401 Unauthorized

  • Missing X-Webhook-Secret header
  • Secret mismatch - regenerate and update external app

See Also

On this page