{"id":19296,"plugin_id":"plugins_6a95e41b8bf08191956b45ae05b391e4","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:15:27.571Z","digest":"6ec754cf724cf9e8b9d6cd4a1f1c606f31654d21a775f03a1cb0e2b03e9f30ef","against":null,"payload":{"description":"This skill should be used to design, build, secure, or debug an Xweather Webhooks receiver — the push alternative to polling the Weather API. Use it whenever a task mentions Xweather webhooks, pushed weather data, a weather webhook receiver or endpoint, subscribing to pushed hail/lightning/alerts/storm-cell data, or asks how to stop polling the Xweather API and receive data in real time instead. Also use it when writing the endpoint handler, choosing a data set to subscribe to, or preparing the registration details Xweather needs. Also covers Xweather's attribution requirement — the 'Powered by Vaisala Xweather' credit and logo rules that apply wherever Xweather data or imagery is displayed.","included_files":[],"name":"webhooks","skill_md_contents":"---\nname: webhooks\ndescription: This skill should be used to design, build, secure, or debug an Xweather Webhooks receiver — the push alternative to polling the Weather API. Use it whenever a task mentions Xweather webhooks, pushed weather data, a weather webhook receiver or endpoint, subscribing to pushed hail/lightning/alerts/storm-cell data, or asks how to stop polling the Xweather API and receive data in real time instead. Also use it when writing the endpoint handler, choosing a data set to subscribe to, or preparing the registration details Xweather needs. Also covers Xweather's attribution requirement — the 'Powered by Vaisala Xweather' credit and logo rules that apply wherever Xweather data or imagery is displayed.\nlicense: MIT\n---\n\n# Xweather Webhooks\n\nXweather pushes data sets to an HTTPS endpoint you own, instead of your application polling\n`data.api.xweather.com` on a timer. The payload is **byte-for-byte the same shape the Weather API\nreturns**, so existing response-parsing code is reusable with almost no change.\n\n**Webhooks are a premium add-on requiring a separate subscription, and endpoints are registered by\nXweather staff — not self-service.** There is no API call or dashboard toggle that turns this on. Say\nso early: the work splits into what the user can build today (the receiver) and what needs a\nconversation with their account executive (the subscription and registration).\n\n## When webhooks are the right answer\n\nReach for them when the user is polling frequently for data that changes unpredictably — lightning\nstrikes, hail threats, new alerts, storm cell updates. Polling that on a short interval burns\naccesses continuously and still adds latency; push removes both problems.\n\nPolling via the `weather-api` skill stays the better fit when the data is requested on\ndemand (a user opens a page), when the update cadence is slow and predictable (daily normals, hourly\ntemperatures), or when the user can't host a public HTTPS endpoint.\n\n## How a delivery works\n\nXweather sends an HTTP `POST` to the registered URL.\n\n| | |\n|---|---|\n| Method | `POST` |\n| `Content-Type` | `application/json` |\n| `x-api-key` | A client-generated shared secret, proving the delivery came from Xweather. May be a query parameter instead of a header, if preferred. |\n| Body | The standard Xweather API response envelope — JSON or GeoJSON, whichever was configured |\n\nThe endpoint must answer with a **2xx** status — `202` is the conventional choice — and do it\n*immediately*. The response body is ignored.\n\n**Acknowledge first, process second.** Any real work belongs in a background job, queue, or thread\nstarted after the status is written. A handler that parses, matches polygons, and sends notifications\nbefore responding will eventually exceed the delivery timeout, and a timeout is treated as a failed\ndelivery.\n\n### Retries\n\nA non-2xx status or a connection timeout triggers a retry, **typically no more than two or three\nattempts**. After that the delivery is marked failed and is not attempted again — the next\nopportunity is the next update for that data set. There is no replay or backfill mechanism, so a\nreceiver that's down during an event has permanently missed it. Two consequences worth raising:\n\n- Deliveries are effectively at-most-once after retries are exhausted. If gapless history matters,\n  pair the webhook with a periodic API query as a reconciliation backstop.\n- Handlers should be **idempotent**, because a retry can duplicate a delivery your server actually\n  did process but was too slow to acknowledge. Key writes on a stable field — station id, alert id,\n  strike timestamp — and upsert rather than insert.\n\n## Available data sets\n\nCommon:\n\n| Data set | What arrives |\n|---|---|\n| Hail Threats | Real-time hail threat polygons with severity ratings |\n| Lightning Threats | Predictive lightning threat zones |\n| Lightning | Individual strike events as they occur |\n| Lightning Analytics | Strike events with enhanced analytical data |\n| Lightning Flash | Consolidated cloud-to-ground flash data |\n| Alerts | Government watches, warnings, and advisories |\n| Fires | Active wildfire perimeters and fire weather |\n| Tropical Cyclones | Storm and hurricane track updates |\n\nAlso available, less commonly used: Air Quality · Earthquakes · Observations · Rivers · Storm\nReports · Storm Cells.\n\nAnything outside both lists needs a support conversation. Each data set corresponds to a Weather API\nendpoint. Use the `weather-api` skill when you need the endpoint's response-field reference.\n\n## Building the receiver\n\nThe whole contract is: accept POST, verify the secret, return 202, process asynchronously.\n\n```javascript\nimport express from 'express';\nconst app = express();\napp.use(express.json());\n\napp.post('/webhooks/xweather/a3f8c2d1e5b7', (req, res) => {\n  if (req.get('x-api-key') !== process.env.XWEATHER_WEBHOOK_KEY) {\n    return res.status(401).end();\n  }\n  res.status(202).end();        // acknowledge first\n  enqueue(req.body);            // then hand off — never process inline\n});\n\napp.listen(3000);\n```\n\n```python\nfrom flask import Flask, request, abort\nimport os, threading\n\napp = Flask(__name__)\n\n@app.route('/webhooks/xweather/a3f8c2d1e5b7', methods=['POST'])\ndef receive_webhook():\n    if request.headers.get('X-Api-Key') != os.environ['XWEATHER_WEBHOOK_KEY']:\n        abort(401)\n    data = request.get_json()\n    threading.Thread(target=process_payload, args=(data,)).start()\n    return '', 202\n```\n\nThe docs' own examples use a bare `threading.Thread` and an unguarded handler. That's fine as an\nillustration, but for anything real prefer a durable queue (SQS, Celery, BullMQ, a database-backed\njob table) over an in-process thread — a thread dies with the process, and the delivery is already\nacknowledged, so the data is simply gone. Raise this when the user is writing production code.\n\nTest locally against the **Xweather Postman collection**, which ships sample payloads for the data\nsets; no subscription needed to exercise the handler.\n\n## Securing the endpoint\n\nLayer these — no single one is sufficient:\n\n1. **HTTPS only.** Non-negotiable: it encrypts the payload and keeps the URL token off the wire.\n2. **A secret token in the URL path** — `https://your-server.com/webhooks/xweather/a3f8c2d1e5b7`.\n   This is obscurity, not authentication: it cuts random scanning and accidental discovery. Useful,\n   but never the only control.\n3. **API key verification.** Xweather includes a client-provided key on every request, as an\n   `X-Api-Key` header (recommended) or an `api_key` query parameter. Compare it on every request and\n   reject mismatches before parsing anything. Use a constant-time comparison if the language offers\n   one.\n4. **Payload validation.** Check `Content-Type` is `application/json`, that the body parses, and that\n   the top-level structure matches the expected envelope for that data set. **Allow unknown extra\n   fields** — Xweather may add fields and commits to never removing them, so a strict schema that\n   rejects unrecognised keys will break on a future release. Validate permissively.\n\nRotating a URL or key is a support request needing **at least two business days' notice** to avoid\ndropped deliveries. Worth designing for: have the receiver accept both the old and new key during a\nrotation window rather than cutting over atomically.\n\n## Registering\n\nXweather configures the subscription. The user supplies:\n\n- **The full HTTPS URL** of each receiver. Use a **fully qualified domain name, not a raw IP** —\n  DNS-based endpoints survive infrastructure changes.\n- **The data set(s)** to subscribe to.\n- **The coverage area** — a bounding box or polygon. Keep it a simple rectangle or low-vertex polygon;\n  complex geometry causes performance problems.\n- **Per-environment URLs and keys.** Up to three environments (dev / staging / production) are\n  supported without discussion; more needs a support conversation.\n- **Format**: JSON or GeoJSON. GeoJSON is the natural choice for polygon data sets (hail threats,\n  alerts, fire perimeters) and for anything heading to a map.\n\nThe registration form Xweather expects looks like this:\n\n| Field | Example |\n|---|---|\n| Client Name | My Weather Company |\n| Webhook Endpoint(s) | Hail Threats |\n| Coverage Area | CONUS — options include CONUS, AK, HI, Puerto Rico, Guam; specify which |\n| Update Interval | Real-time |\n| Format | GeoJSON |\n| Client Endpoints | staging: `https://example.com/webhooks/staging/kawrejhg8a`<br>production: `https://example.com/webhooks/production/jwer9024hf` |\n| Authentication | staging `X-API-KEY: 56c3edd0…`<br>production `X-API-KEY: 2001e097…` |\n\nGenerate **different secrets per environment** — a staging key that also unlocks production defeats\nthe purpose. When helping fill this in, produce the structure and let the user paste in their own\nsecrets rather than inventing key values for them.\n\nXweather sends a **test payload before enabling the full data set**, so the first thing to watch for\nafter registration is that single delivery landing and being acknowledged.\n\n## Debugging\n\n| Symptom | Likely cause |\n|---|---|\n| No deliveries at all | Subscription not yet enabled, or still awaiting the test payload. Registration is manual — confirm with the account executive before debugging code. |\n| Deliveries stop after a burst | Handler returned non-2xx or timed out, retries exhausted. Check that 202 is written *before* processing. |\n| Duplicate records | Handler isn't idempotent and a slow acknowledgement triggered a retry. Upsert on a stable key. |\n| Payload parses but fields are missing | Wrong `format` configured (JSON vs GeoJSON), or the data set differs from what was expected. |\n| Handler breaks after working for months | Strict schema validation rejecting newly added fields. Validate permissively. |\n| Endpoint receiving junk traffic | URL token leaked, or no key check. Add `X-Api-Key` verification and request a rotation. |\n| Works locally, not in production | Endpoint not publicly reachable over HTTPS, or a proxy/load balancer stripping the `x-api-key` header. |\n\nBecause the payload matches the API response, a fast way to know what to expect is to query the\nequivalent Weather API endpoint once via the `weather-api` skill and inspect the response shape.\n\n## Common patterns\n\n- **Severe weather alerting** — subscribe to Alerts, Hail Threats, or Lightning Threats; on delivery,\n  test the threat polygon against your asset/customer locations and notify whoever falls inside.\n- **Real-time lightning tracking** — subscribe to Lightning or Lightning Analytics; feed strikes into\n  a map layer or analytics pipeline. Note the Weather API charges ×10 for lightning; push avoids the\n  repeated polling cost entirely.\n- **Observation ingestion** — subscribe to Observations with a bounding box and upsert by station id\n  to keep a local mirror current.\n- **Storm cell monitoring** — subscribe to Storm Cells for position, movement vector, intensity, and\n  hail probability; drive dispatch alerts or worksite closures.\n- **Fire weather operations** — combine Fires (perimeters) with Alerts (red flag warnings) to\n  automate escalation.\n- **Flood and river operations** — subscribe to Rivers, compare stage readings against each gauge's\n  flood stage, trigger downstream workflows.\n\n## Attribution is required\n\nXweather requires attribution wherever its data or imagery is displayed. This applies to **all\nproducts** — Weather API, Raster Maps, and MapsGL alike. Build it into anything you produce, and say\nso when handing over code or URLs that will end up in front of users.\n\nThe minimum is a link to `https://www.xweather.com/` reading \"Powered by Vaisala Xweather\":\n\n```html\n<a href=\"https://www.xweather.com/\" target=\"_blank\" title=\"Powered by Vaisala Xweather\">Powered by Vaisala Xweather</a>\n```\n\nThe logo may be substituted for the \"Xweather\" text. Light and dark variants exist in SVG and PNG:\n\n```html\n<a href=\"https://www.xweather.com/\" target=\"_blank\" title=\"Powered by Vaisala Xweather\">\n  <img src=\"https://www.xweather.com/assets/logos/vaisala-xweather-logo-dark.svg\" alt=\"Vaisala Xweather\" height=\"40\" />\n</a>\n```\n\nSwap `-dark` for `-light` over a dark background, or `.svg` for `.png`. Using the logo brings rules:\nkeep it unmodified, leave at least a **10px buffer** of space around it, and only adjust lightness or\nopacity in greyscale. Don't rotate it, don't recolour it (monotone black or white excepted), and don't\nuse the symbol without the Xweather name.\n\nFull guide: https://www.xweather.com/docs/weather-api/resources/attribution\n\n## Related\n\nThe `weather-api` skill covers the pull equivalent and is the reference for payload field names —\nevery webhook data set mirrors an endpoint documented there. Its `access-cost.md` explains the\npolling cost that webhooks are often adopted to eliminate.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}