dynamical.org pipeline webhooks

← back to webhooks

function examples

A subscription's function runs in a sandbox when a matching dynamical.org data product event fires. You write only the body of handler(event, ds) — the imports and signature are fixed:

import numpy as np
import pandas as pd
import xarray as xr
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

def handler(event, ds):
    ...your body...

event is the milestone payload (event_id, product_id, init_time, …); ds is that product's dataset, already opened as an xarray.Dataset. Your return value is placed on the result key of the webhook body; return None to skip the delivery. To send an image, base64-encode it and return the string.

event["init_time"] is an ISO-8601 string with a trailing Z. Strip it and select with method="nearest"ds.sel(init_time=event["init_time"].replace("Z", ""), method="nearest"): the Z makes the label tz-aware so it won't compare against the dataset's tz-naive index, and nearest covers the test run's synthetic init before any real event has fired (e.g. the current hour, when the product only runs 00/06/12/18Z). The examples below are written for noaa-gfs-forecast, whose temperature_2m is in °C and whose longitudes run −180..180; both vary by dataset.

1. Maximum 2 m temperature

Reduce the run to a single number and return it as a small JSON object.

init = event["init_time"].replace("Z", "")
t2m = ds["temperature_2m"].sel(init_time=init, method="nearest").max().item()
return {"max_temperature_2m_c": round(t2m, 2)}

2. Notify only on a threshold skip

Return None to drop the delivery — the function doubles as a filter.

init = event["init_time"].replace("Z", "")
t2m = ds["temperature_2m"].sel(init_time=init, method="nearest").max().item()
if t2m < 40:            # < 40°C: nothing notable, skip
    return None
return {"max_temperature_2m_c": round(t2m, 2)}

3. Regional summary with xarray

Subset to a bounding box and return a few aggregates.

init = event["init_time"].replace("Z", "")
da = ds["temperature_2m"].sel(init_time=init, method="nearest")
conus = da.sel(latitude=slice(50, 24), longitude=slice(-125, -65))
return {
    "conus_mean_c": round(float(conus.mean()), 2),
    "conus_max_c": round(float(conus.max()), 2),
}

4. Map plot → base64 PNG matplotlib

Plot a field with matplotlib, render to PNG in memory, and return the base64 string on result. (Import base64 and io in your body — only numpy/pandas/xarray/matplotlib are pre-imported.)

import base64, io

init = event["init_time"].replace("Z", "")
da = ds["temperature_2m"].sel(init_time=init, method="nearest").isel(lead_time=0)
fig, ax = plt.subplots(figsize=(8, 4))
da.plot(ax=ax)
ax.set_title(f"{event['product_id']} t2m @ {event['init_time']}")

buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=100, bbox_inches="tight")
plt.close(fig)
return {"plot_png_b64": base64.b64encode(buf.getvalue()).decode()}

5. Forecast time series at a point → base64 PNG matplotlib

Pull the lead-time series at one location and plot it against valid_time (a clean date axis — plotting the raw lead_time renders as nanoseconds). Set the title explicitly, or xarray's auto-title lists every scalar coordinate (e.g. ingested_forecast_length=NaT).

import base64, io

init = event["init_time"].replace("Z", "")
series = ds["temperature_2m"].sel(
    init_time=init, latitude=40.0, longitude=-105.0, method="nearest"
)
fig, ax = plt.subplots(figsize=(8, 3))
series.plot(ax=ax, x="valid_time")
ax.set_title(f"{event['product_id']} 2 m temperature @ 40N, 105W")
ax.set_ylabel("2 m temperature (°C)")

buf = io.BytesIO()
fig.savefig(buf, format="png", bbox_inches="tight")
plt.close(fig)
return {"point_forecast_png_b64": base64.b64encode(buf.getvalue()).decode()}

Variable names, coordinates, and units vary by dataset — browse a product at dynamical.org/catalog. Use the editor's test run to preview the exact body before saving.