{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Webhook Code Samples — kwery Docs","siteUrl":"https://docs.kwery.co","description":"Submit batch jobs and stream requests in cURL, Node.js, and Python — working code for every kwery endpoint."},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"code-samples","__idx":0},"children":["Code samples"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Copy-paste examples in cURL, Node.js, and Python. Replace credentials and the host as needed."," ","All examples assume ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://api.kwery.co"]}," and query ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/sources/idealo"},"children":["Idealo"]}," — swap"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["source"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["country"]}," for any other ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/sources"},"children":["source"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"submit-a-batch-job","__idx":1},"children":["Submit a batch job"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"curl","__idx":2},"children":["cURL"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -s https://api.kwery.co/job \\\n  -H \"Authorization: Bearer $KWERY_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"source\":\"idealo\",\"country\":\"de\",\"key\":\"gtin\",\"values\":[\"4006381333962\"]}'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"nodejs","__idx":3},"children":["Node.js"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const res = await fetch('https://api.kwery.co/job', {\n  method: 'POST',\n  headers: {\n    'Authorization': `Bearer ${process.env.KWERY_API_KEY}`,\n    'Content-Type': 'application/json',\n  },\n  body: JSON.stringify({ source: 'idealo', country: 'de', key: 'gtin', values: ['4006381333962'] }),\n});\nconst data = await res.json();\nif (data.error) throw new Error(data.message);\nconsole.log('job id:', data.job._id);\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"python","__idx":4},"children":["Python"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import os, requests\n\nres = requests.post(\n    \"https://api.kwery.co/job\",\n    headers={\"Authorization\": f\"Bearer {os.environ['KWERY_API_KEY']}\"},\n    json={\"source\": \"idealo\", \"country\": \"de\", \"key\": \"gtin\", \"values\": [\"4006381333962\"]},\n)\ndata = res.json()\nif data.get(\"error\"):\n    raise RuntimeError(data[\"message\"])\nprint(\"job id:\", data[\"job\"][\"_id\"])\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"poll-then-download","__idx":5},"children":["Poll, then download"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"curl-1","__idx":6},"children":["cURL"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"JOB=64f1c2d3e4b5a6c7d8e9f0a1\ncurl -s \"https://api.kwery.co/job/$JOB\"          -H \"Authorization: Bearer $KWERY_API_KEY\"   # status\ncurl -s \"https://api.kwery.co/job/$JOB/download\" -H \"Authorization: Bearer $KWERY_API_KEY\"   # results\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"nodejs-1","__idx":7},"children":["Node.js"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"async function waitAndDownload(jobId) {\n  const headers = { 'Authorization': `Bearer ${process.env.KWERY_API_KEY}` };\n  for (;;) {\n    const job = await (await fetch(`https://api.kwery.co/job/${jobId}`, { headers })).json();\n    if (job.status === 'finished') break;\n    await new Promise(r => setTimeout(r, 3000));\n  }\n  return (await fetch(`https://api.kwery.co/job/${jobId}/download`, { headers })).json();\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"python-1","__idx":8},"children":["Python"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import time\n\nHEADERS = {\"Authorization\": f\"Bearer {os.environ['KWERY_API_KEY']}\"}\n\ndef wait_and_download(job_id):\n    while True:\n        job = requests.get(f\"https://api.kwery.co/job/{job_id}\", headers=HEADERS).json()\n        if job.get(\"status\") == \"finished\":\n            break\n        time.sleep(3)\n    return requests.get(f\"https://api.kwery.co/job/{job_id}/download\", headers=HEADERS).json()\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"submit-a-stream-job","__idx":9},"children":["Submit a stream job"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"curl-2","__idx":10},"children":["cURL"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -s https://api.kwery.co/stream \\\n  -H \"Authorization: Bearer $KWERY_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"source\":\"idealo\",\"country\":\"de\",\"key\":\"gtin\",\n    \"values\":[\"4006381333962\",\"4719512101148\"],\n    \"callback_url\":\"https://your-app.example.com/kwery/webhook\"\n  }'\n# → { \"error\": false, \"job_id\": \"...\", \"webhook_secret\": \"...\" }\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Store the returned ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["webhook_secret"]}," — you need it to verify deliveries."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"verify-a-webhook-delivery","__idx":11},"children":["Verify a webhook delivery"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The signature is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["v1="]}," followed by the HMAC-SHA256 of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"{timestamp}.{raw_body}\""]},", keyed with"," ","your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["webhook_secret"]},". ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Verify against the raw body, before JSON parsing."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"nodejs-express","__idx":12},"children":["Node.js (Express)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const express = require('express');\nconst crypto = require('crypto');\n\nconst SECRET = process.env.KWERY_WEBHOOK_SECRET;  // from the POST /stream response\nconst app = express();\n\n// Capture the raw body for signature verification.\napp.use('/kwery/webhook', express.raw({ type: 'application/json' }));\n\napp.post('/kwery/webhook', (req, res) => {\n  const rawBody = req.body;                                   // Buffer\n  const sig     = req.get('X-Kwery-Signature') || '';\n  const ts      = req.get('X-Kwery-Timestamp') || '';\n\n  // 1. Reject stale deliveries (replay protection).\n  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {\n    return res.status(401).end('stale timestamp');\n  }\n\n  // 2. Recompute and compare in constant time.\n  const expected = 'v1=' + crypto.createHmac('sha256', SECRET)\n    .update(`${ts}.${rawBody.toString('utf8')}`)\n    .digest('hex');\n  const ok = sig.length === expected.length &&\n    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));\n  if (!ok) return res.status(401).end('bad signature');\n\n  // 3. Acknowledge fast, then process asynchronously.\n  res.status(202).end('ok');\n\n  const payload = JSON.parse(rawBody.toString('utf8'));\n  for (const item of payload.results || []) {\n    // handle item.result.content ...\n  }\n});\n\napp.listen(9000);\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"python-flask","__idx":13},"children":["Python (Flask)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import hmac, hashlib, time, os\nfrom flask import Flask, request\n\nSECRET = os.environ[\"KWERY_WEBHOOK_SECRET\"].encode()\napp = Flask(__name__)\n\n@app.post(\"/kwery/webhook\")\ndef webhook():\n    raw = request.get_data()                       # raw bytes\n    sig = request.headers.get(\"X-Kwery-Signature\", \"\")\n    ts  = request.headers.get(\"X-Kwery-Timestamp\", \"\")\n\n    # 1. Replay protection.\n    if abs(time.time() - float(ts or 0)) > 300:\n        return \"stale timestamp\", 401\n\n    # 2. Constant-time signature check.\n    expected = \"v1=\" + hmac.new(\n        SECRET, f\"{ts}.{raw.decode()}\".encode(), hashlib.sha256\n    ).hexdigest()\n    if not hmac.compare_digest(sig, expected):\n        return \"bad signature\", 401\n\n    # 3. Acknowledge, then process.\n    payload = request.get_json()\n    for item in payload.get(\"results\", []):\n        pass  # handle item[\"result\"][\"content\"] ...\n    return \"ok\", 202\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"see-also","__idx":14},"children":["See also"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/webhooks"},"children":["Webhooks"]}," — payload shape, retries, replay, and polling fallback."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/authentication"},"children":["Authentication"]}," — API keys, and the legacy options."]}]}]},"headings":[{"value":"Code samples","id":"code-samples","depth":1},{"value":"Submit a batch job","id":"submit-a-batch-job","depth":2},{"value":"cURL","id":"curl","depth":3},{"value":"Node.js","id":"nodejs","depth":3},{"value":"Python","id":"python","depth":3},{"value":"Poll, then download","id":"poll-then-download","depth":2},{"value":"cURL","id":"curl-1","depth":3},{"value":"Node.js","id":"nodejs-1","depth":3},{"value":"Python","id":"python-1","depth":3},{"value":"Submit a stream job","id":"submit-a-stream-job","depth":2},{"value":"cURL","id":"curl-2","depth":3},{"value":"Verify a webhook delivery","id":"verify-a-webhook-delivery","depth":2},{"value":"Node.js (Express)","id":"nodejs-express","depth":3},{"value":"Python (Flask)","id":"python-flask","depth":3},{"value":"See also","id":"see-also","depth":2}],"frontmatter":{"seo":{"title":"Webhook Code Samples — kwery Docs","description":"Submit batch jobs and stream requests in cURL, Node.js, and Python — working code for every kwery endpoint."}},"lastModified":"2026-09-14T12:09:30.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/code-samples","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}