Condition codes are reactive by design. By the time condition.code returns 1087 (thundery outbreaks nearby) or 1273 (patchy light rain with thunder), the storm is already in progress. For most consumer apps that’s fine. For anything where someone might be outdoors, operating equipment, or routing a delivery — that’s too late.
The fields that give you earlier signal are already in the response. You just have to combine them deliberately rather than waiting for the condition code to update.
What You’re Actually Trying to Detect
Thunderstorm development in a mid-latitude environment almost always involves three preconditions running simultaneously: moisture in the low-to-mid atmosphere, a lifting mechanism, and atmospheric instability. WeatherAPI doesn’t expose CAPE or a lifted index directly — those require sounding data. But it does give you surface-level proxies that correlate with each of those preconditions.
The three fields that do real work here are pressure_mb (tracked across sequential requests), humidity, and dewpoint_c. None of them alone is sufficient. Together, with a threshold check, they flag the setup before the event.
Pressure Trend: The Part Everyone Underuses
A single pressure reading tells you almost nothing about storm risk. The rate of change is what matters. A drop of roughly 1.5–2 hPa over a one-hour window is the standard meteorological threshold for a notable pressure fall — the kind associated with an approaching trough or deepening surface low. NWS surface analysis guidance uses similar thresholds when flagging rapid intensification.
WeatherAPI’s real-time endpoint doesn’t return a calculated pressure tendency field, so you track it yourself. That means storing the last reading with a timestamp and computing the delta on your side. It’s two extra lines in whatever your polling loop looks like, and it’s the only way to get a leading indicator rather than a coincident one.
A simple approach in Python:
import time
prev_pressure = None
prev_ts = None
def check_pressure_fall(current_mb, current_ts):
global prev_pressure, prev_ts
if prev_pressure is None:
prev_pressure = current_mb
prev_ts = current_ts
return 0.0
elapsed_hours = (current_ts - prev_ts) / 3600
if elapsed_hours == 0:
return 0.0
rate = (prev_pressure - current_mb) / elapsed_hours # positive = falling
prev_pressure = current_mb
prev_ts = current_ts
return rate
If check_pressure_fall() returns above ~1.5 over a one-hour window, that’s your first flag.
Dewpoint and the Moisture Threshold
High humidity alone is misleading — 90% relative humidity at 5°C carries very little convective potential. Dewpoint gives you absolute moisture content independent of temperature. The rough heuristic in surface-based convective forecasting is a dewpoint above 13°C as a meaningful lower bound for moisture supporting thunderstorm development at mid-latitudes; in summer, 16°C or higher is a stronger signal.
WeatherAPI returns dewpoint_c in both the hourly forecast and the current conditions response. Pull it directly — don’t derive it from temperature and humidity yourself, because the rounding in the humidity field introduces enough noise to matter at the margins.
Humidity is still useful, just for a different purpose: high relative humidity combined with a high dewpoint means the atmosphere is close to saturation from the surface up. That’s the environment where a lifting trigger — a sea breeze front, an outflow boundary, a small pressure fall — is most likely to produce convection rather than just cloud.
Putting a Composite Score Together
The simplest version is a boolean: three conditions, all must be true. Not subtle, but it works as a first pass without over-engineering.
def thunderstorm_risk_elevated(current, pressure_fall_rate):
"""
current: dict of current conditions from WeatherAPI response
pressure_fall_rate: hPa/hour, positive = falling
"""
pressure_falling = pressure_fall_rate >= 1.5
moist_surface = current['dewpoint_c'] >= 13.0
near_saturation = current['humidity'] >= 70
return pressure_falling and moist_surface and near_saturation
You can weight these if you want a scored output instead — assign each condition a point value, sum them, return a risk tier. That’s more useful if you want a “watch” state (two of three met) versus a “warning” state (all three). The boolean version catches the obvious cases and avoids false positives from any single field running high in isolation.
One Caveat Worth Being Direct About
This approach detects a favorable thermodynamic environment. It does not detect an actual storm. There’s a real difference between conditions that support convective development and conditions where a storm is actually initiating or organized. For that you’d want radar data, lightning network feeds (Vaisala’s Global Lightning Dataset and ENTLN are the two main commercial options), or CAPE/LI values from mesoscale model output — none of which WeatherAPI provides.
What this composite check gives you is a window of elevated risk: a “heads up, the environment is primed” signal that tends to arrive roughly 30–90 minutes before a condition code update, depending on how fast the situation is evolving. For an outdoor events app, a construction site safety alert, or a delivery routing system, that’s actionable. Don’t surface it to users as storm detection — it isn’t that.
Also: this works best at locations with decent station density. Where the nearest METAR is 40+ km away, the current pressure and dewpoint readings may not fully represent local conditions. That’s not solvable at the API layer — it’s a station network limitation. Know your locations, and weight the output less confidently where coverage is thin.
Where to Hook This Into a Forecast Request
The real-time endpoint (/current.json) gives you current surface conditions. The forecast endpoint (/forecast.json) gives you hourly dewpoint and humidity out to 14 days. For a forward-looking risk window — flagging the next 6 hours for an outdoor event, say — pull the hourly forecast data, run the moisture and humidity checks against each hour, and compute pressure tendency from consecutive hours in the hourly array the same way you would from live readings: pressure_mb from hour N minus hour N+1. It’s a model projection rather than a live observation, but it gives you directional signal over the forecast window.
Cache the hourly forecast response aggressively. The thermodynamic environment changes on a timescale of hours, not minutes — a 15-minute cache is more than sufficient, and it keeps quota consumption reasonable across many locations.
If you’re already storing pressure readings for trend calculation, the incremental cost of this check is minimal. The harder part is deciding what to do with the elevated-risk flag in your product — that’s a UX question, not a data one, and it’s worth thinking through before you ship it.
