Kushwaha Tech Solutions / Developer guide

One place for every inquiry.

Connect your website backend to Kushwaha Leads. Authenticate each website separately and save validated inquiries in a private database.

API v1 · Database storage only

How it works

Visitor → Your website form → Your backend → Kushwaha Leads API → Database

Base URL: https://leads.kushwahatechsolutions.com/api/v1

Keep your key on your server. Never put it in browser JavaScript, HTML, a mobile app, or a public environment variable. Origin headers and CORS do not authenticate a website.

A successful response means the inquiry has been stored. Email notifications are currently disabled. Your website should validate its form and apply its own spam controls and visitor rate limits.

Submit an inquiry

POST /api/v1/forms/YOUR_FORM_PUBLIC_ID/submissions
Authorization: Bearer YOUR_WEBSITE_API_KEY
Content-Type: application/json
Accept: application/json
Idempotency-Key: unique-id-for-this-inquiry
{
  "name": "John Smith",
  "email": "john@example.com",
  "phone": "+14155550123",
  "subject": "Website development",
  "message": "I need a Laravel website.",
  "fields": {"service": "Laravel", "budget": "5000–10000"}
}

Required: a valid email or phone, plus a nonblank message (maximum 5,000 characters). Name and subject allow 255 characters; phone allows 50. Requests must be JSON objects and no larger than 32 KB.

fields is optional: an object of at most 20 keys. Names start with a letter and contain up to 64 letters, numbers, underscores or hyphens. Values are scalars (strings up to 1,000 characters) or arrays of up to 10 scalars. Nested objects, unknown top-level fields, and uploads are rejected.

201 · Saved

{"success":true,"message":"Your inquiry has been submitted successfully.","data":{"submission_id":1024}}

Safe retries

Use an optional Idempotency-Key of 1–100 printable ASCII characters, without spaces. Keep the same key and content when retrying one inquiry. A replay returns the original ID and 201; different content with that key returns 409. Keys are scoped to the website and form and retained until the lead is deleted. Use a new key for each new inquiry.

Without an idempotency key, retries can create duplicates. A timeout does not prove that storage failed. Preserve the inquiry and its key locally if reliable retries are needed.

Server-side integration examples

cURL

curl 'https://leads.kushwahatechsolutions.com/api/v1/forms/YOUR_FORM_PUBLIC_ID/submissions' \
  -H 'Authorization: Bearer YOUR_WEBSITE_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'Idempotency-Key: inquiry-unique-123' \
  --data '{"email":"john@example.com","message":"Please contact me."}'

Laravel website

Put the URL, key and form ID in private server configuration. Map your environment values to config/services.php as services.kushwaha_leads. The inquiry ID below must come from your locally persisted inquiry or validated server-issued form token and remain stable across retries.

try {
    $response = Http::withToken(config('services.kushwaha_leads.api_key'))
        ->acceptJson()
        ->withHeaders(['Idempotency-Key' => $inquiryId])
        ->connectTimeout(3)->timeout(10)
        ->post(
            rtrim(config('services.kushwaha_leads.url'), '/')
            . '/api/v1/forms/' . config('services.kushwaha_leads.form_id')
            . '/submissions',
            $validated
        );
} catch (\Illuminate\Http\Client\ConnectionException $e) {
    return back()->with('error', 'Unable to confirm storage. Please try again.');
}

if ($response->status() === 201) {
    return back()->with('success', 'Your inquiry has been saved.');
}
return back()->with('error', 'Unable to save your inquiry. Please try again.');

Plain PHP backend

Your form posts to your own backend. Validate input, check CSRF and spam controls, and keep the credential in server environment variables. Never forward the API credential to the browser.

$curl = curl_init(getenv('KUSHWAHA_LEADS_URL')
    . '/api/v1/forms/' . getenv('KUSHWAHA_LEADS_FORM_ID') . '/submissions');
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 3,
    CURLOPT_TIMEOUT => 10,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('KUSHWAHA_LEADS_API_KEY'),
        'Content-Type: application/json',
        'Accept: application/json',
        'Idempotency-Key: ' . $inquiryId,
    ],
    CURLOPT_POSTFIELDS => json_encode($validated, JSON_THROW_ON_ERROR),
]);
$result = curl_exec($curl);
$stored = $result !== false && curl_getinfo($curl, CURLINFO_RESPONSE_CODE) === 201;
curl_close($curl);
// Only show success when $stored is true. Retain inquiry ID for retries.

Errors and troubleshooting

{"success":false,"message":"Request rejected.","data":null}
StatusMeaning / next step
401Missing, invalid, revoked or expired key. Check your server configuration.
403Disabled website or insufficient owner authorization.
404Form missing, disabled, or belongs to another website; or owner record missing.
409Idempotency key already used for different content.
413Request exceeds 32 KB.
415Use Content-Type: application/json.
422Validation failed. Check errors and fix the request.
429Rate limited. Respect Retry-After and retry with the same inquiry key.
500Unexpected service error. Retain the inquiry for a safe retry.

Default submission limits: 100/minute per website and per key, with a 200/minute source-IP ingress limit. The source IP seen here is usually your server’s IP. Your origin form should also apply a visitor limit (for example 10/minute).

Private owner API

Owner routes require a separate Sanctum token with the owner ability and an authorized owner account. Website submission keys cannot read or manage leads.

Authorization: Bearer OWNER_TOKEN
MethodPath under /api/v1
GET/websites
GET/websites/{id}
GET/websites/{id}/forms
GET/websites/{id}/submissions
GET/websites/{id}/stats
GET/forms/{id}/submissions
GET/submissions
GET/submissions/{id}
PATCH/submissions/{id}/status
DELETE/submissions/{id}
GET/stats

Owner routes use numeric IDs. Lead filters: website_id, form_id, status, search, from, to, page, and per_page (default 20, maximum 100). Dates use YYYY-MM-DD, inclusive in Asia/Kolkata by default. Results sort newest first by timestamp and ID.

Status body: {"status":"contacted"}. Allowed statuses: new, contacted, follow_up, converted, closed. Deletion permanently removes a lead and returns 204 with no body. Stats use the configured reporting timezone; stored timestamps use UTC.