Skip to content

Grants by agency — track new grant opportunities

Get a webhook when new grant opportunities post at agencies you care about, optionally narrowed by CFDA number, applicant type, or response window.

The 1-line answer

Create a filter alert on query_type=grant. Filters are the same query parameters you'd pass to /api/grants/.

curl -X POST -H "X-API-KEY: $TANGO_API_KEY" \
  -H "Content-Type: application/json" \
  "https://tango.makegov.com/api/webhooks/alerts/" \
  -d '{
    "name": "DHS cybersecurity grants",
    "query_type": "grant",
    "filters": {
      "agency": "DHS",
      "cfda_number": "97.067"
    },
    "frequency": "daily"
  }'

That's it. Tango re-evaluates daily and POSTs alerts.grant.match events when new grants match.

Step 1 — Verify the filter against the grants API

curl -H "X-API-KEY: $TANGO_API_KEY" \
  "https://tango.makegov.com/api/grants/?agency=DHS&cfda_number=97.067&ordering=-posted_date&limit=5"

If this returns the grants you expect, the alert will fire when new ones land.

Step 2 — Create the alert

curl -X POST -H "X-API-KEY: $TANGO_API_KEY" \
  -H "Content-Type: application/json" \
  "https://tango.makegov.com/api/webhooks/alerts/" \
  -d '{
    "name": "DHS cybersecurity grants — open in next 60d",
    "query_type": "grant",
    "filters": {
      "agency": "DHS",
      "cfda_number": "97.067",
      "applicant_types": "00",
      "response_date_after": "2026-05-12",
      "response_date_before": "2026-07-12"
    },
    "frequency": "daily"
  }'
import os
from datetime import date, timedelta
from tango import TangoClient

client = TangoClient(api_key=os.environ["TANGO_API_KEY"])

today = date.today()
sixty_days = today + timedelta(days=60)

alert = client.create_webhook_alert(
    name="DHS cybersecurity grants — open in next 60d",
    query_type="grant",
    filters={
        "agency": "DHS",
        "cfda_number": "97.067",
        "applicant_types": "00",                  # State governments
        "response_date_after": today.isoformat(),
        "response_date_before": sixty_days.isoformat(),
    },
    frequency="daily",
)
print(alert.alert_id, alert.status)
import { TangoClient } from "@makegov/tango-node";

const client = new TangoClient({ apiKey: process.env.TANGO_API_KEY! });

const today = new Date().toISOString().slice(0, 10);
const sixtyDays = new Date(Date.now() + 60 * 24 * 3600 * 1000)
  .toISOString()
  .slice(0, 10);

const alert = await client.createWebhookAlert({
  name: "DHS cybersecurity grants — open in next 60d",
  query_type: "grant",
  filters: {
    agency: "DHS",
    cfda_number: "97.067",
    applicant_types: "00",                       // State governments
    response_date_after: today,
    response_date_before: sixtyDays,
  },
  frequency: "daily",
});
console.log(alert.alert_id, alert.status);

Step 3 — Available grant filters

These are the keys accepted by /api/grants/ and therefore by query_type=grant alerts.

Filter Notes
search Full-text search across title and description (vector-backed).
agency Substring match on agency abbreviation. "DHS" matches DHS-FEMA, DHS-CISA, etc.
opportunity_number Exact opportunity number.
cfda_number CFDA number, substring match (e.g. "97.067").
status Opportunity status (case-insensitive choice).
applicant_types Eligibility / applicant type code (case-insensitive choice).
funding_categories Funding category codes.
funding_instruments Funding instrument codes.
posted_date_after / posted_date_before Posted date range (YYYY-MM-DD).
response_date_after / response_date_before Application deadline range (YYYY-MM-DD).

Filter names matter

The filter is cfda_number (not cfda) and applicant_types (not eligibility). Using the wrong key returns 400.

For full filter docs, see the Grants API reference.

Step 4 — Receive alerts.grant.match events

{
  "timestamp": "2026-05-12T08:00:14Z",
  "delivery_id": "8c5e3f6a-...-9b21",
  "events": [
    {
      "event_type": "alerts.grant.match",
      "alert_id": "e4c4...-...-...",
      "query_type": "grant",
      "filters": {
        "agency": "DHS",
        "cfda_number": "97.067"
      },
      "matches": {
        "new_count": 2,
        "modified_count": 1,
        "new": [
          {"grant_id": "...", "opportunity_number": "DHS-26-CISA-067-001", "title": "Cybersecurity Grant Program FY26", "agency_code": "DHS-CISA"},
          {"grant_id": "...", "opportunity_number": "DHS-26-CISA-067-002", "title": "State and Local Cybersecurity Pilot", "agency_code": "DHS-CISA"}
        ],
        "modified": [
          {"grant_id": "...", "opportunity_number": "DHS-25-CISA-067-014", "title": "...", "agency_code": "DHS-CISA"}
        ]
      },
      "checked_at": "2026-05-12T08:00:12.000Z"
    }
  ]
}

Pull full grant details from /api/grants/{grant_id}/ (or via the SDK's client.get_grant(grant_id) / client.getGrant(grantId)) when you need more than the summary fields.

Limitations

  • No multi-value agency shortcut. agency is a substring match; if you need to watch DHS and HHS, create two alerts (or use search= with a broader term).
  • Polling-friendly cadences. Grants don't post every minute — daily or weekly is usually plenty. realtime works but most agencies refresh on a daily cycle anyway.
  • Tier caps apply. Free 1 / Micro 3 / Small 5 / Medium 10 / Large 25 simultaneous alerts.