Subscribe to an app's installs or removals

POST/v1/webhooks

Subscribes your URL to one app's installs or removals; each change our scans detect is POSTed to you for 10 credits.

View as MarkdownOpenAPI specAll docs for LLMs

  • Auth: API key (Authentication)
  • Scope: events:read
  • Cost: Free to create; 10 credits per change delivered, refunded if delivery fails.

Use it instead of pulling an app's list or GET /v1/events on a schedule and diffing. Keep the secret from the response: it is shown once and signs every delivery (see Webhooks).

From now on, each time a scan detects a store installing or removing app (filtered by types and the optional company filters), we POST it to url within about a minute of the scan, as a WebhookPayload whose data is the same row GET /v1/events returns. Scans are weekly per store, so a change reaches you within a week of happening rather than whenever you next pull and diff a list.

Price: free to create; 10 credits per change delivered (one store, one install or removal), taken before the first send and refunded if every retry fails. A change your balance cannot cover waits (awaiting_credits) and is sent once you top up, for up to 7 days. Needs an account that has bought a credit pack (paid_plan_required otherwise). At most 25 active webhooks per account.

Delivery: url must be https on port 443 at a public address. Any 2xx is success; anything else, a timeout (10s) or a redirect is retried after 1, 5 and 30 minutes, 2, 6, 12 and 24 hours. After 3 deliveries in a row fail every retry, the webhook is disabled. Deliveries may arrive out of order and, rarely, more than once: deduplicate on the Installgraph-Webhook-Id header (the payload's id).

Verifying: Installgraph-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of <t>.<raw body> keyed by the secret returned here (shown once). Compare in constant time and refuse a t more than 5 minutes old. Scope events:read.

Request

curl -X POST "https://api.installgraph.com/v1/webhooks" \
  -H "Authorization: Bearer $INSTALLGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"app":"klaviyo","url":"https://example.com/hooks/installgraph","types":["removed"],"country":"US"}'

Body

JSON.

Field Type Description
app string An app slug from GET /v1/apps. Example: klaviyo.
url string (uri) Example: https://example.com/hooks/installgraph.
types string[] One of installed, removed.
country string As on GET /v1/events.
region Region One of north-america, latin-america, europe, middle-east-africa, asia-pacific.
industry string As on GET /v1/events.
aov string As on GET /v1/events.
description string

Response

200. The example's stores are fictional.

{
  "data": {
    "id": "6731b2e4c9a1f20d8e4b7c15",
    "app": {
      "slug": "klaviyo",
      "name": "Klaviyo",
      "category": "email-sms"
    },
    "types": [
      "removed"
    ],
    "filters": {
      "country": "US"
    },
    "url": "https://example.com/hooks/installgraph",
    "description": null,
    "status": "active",
    "disabledReason": null,
    "creditsPerDelivery": 10,
    "createdAt": "2026-11-10T12:00:00.000Z",
    "lastDeliveredAt": null,
    "secret": "whsec_3q2-7wEXAMPLEc9Lr0m4NnT1vYbZk8sFhGdJpQ6uXaW0"
  },
  "meta": {
    "requestId": "6f1c2a9e-4b7d-4e2a-9c51-0d3b8e7f2a14",
    "creditsUsed": 0,
    "creditsCharged": 0,
    "creditsRemaining": 2490,
    "freeRepulls": 0
  }
}

Fields

Field Type Description
data.id string
data.app AppSummary
data.app.slug string Example: klaviyo.
data.app.name string The catalogue's name: our own for apps we curate, else the App Store listing title as written. Example: Klaviyo.
data.app.displayName string What a merchant calls the app, read from its keyword-padded App Store title ("Preorder, Back In Stock ‑ STOQ" is STOQ). A developer's listings that share a brand keep the words that tell them apart (Swym Wishlist Plus). Equal to name until derived. Example: Klaviyo.
data.app.category Category One of 28 values (see Data model).
data.app.kind string shopify-app: an app installed from Shopify. web: a website technology installed outside Shopify, by pasting its tag into the theme or through a tag manager (Google Tag Manager, Meta Pixel, Hotjar). A vendor with both has one entry of each (meta-pixel and meta-facebook-instagram-app). One of shopify-app, web.
data.types string[] One of installed, removed.
data.filters object The company filters it was created with.
data.filters.country string
data.filters.region string
data.filters.industry string
data.filters.aov string
data.url string
data.description string | null
data.status string disabled after 3 deliveries in a row failed every retry; create a new one. One of active, disabled.
data.disabledReason string | null
data.creditsPerDelivery integer Example: 10.
data.createdAt string (date-time)
data.lastDeliveredAt string (date-time) | null
data.secret string Example: whsec_….

Every keyed response also carries meta (requestId, creditsUsed, creditsRemaining, freeRepulls) and the X-Credits-* and X-RateLimit-* headers. See Credits and Rate limits.

Errors

Status Code Meaning What to do
400 invalid_request A parameter or body field is missing or malformed. Fix the request; message names the field. Do not retry unchanged.
401 missing_api_key No key was sent. Send Authorization: Bearer sfs_… or X-API-Key: sfs_….
401 invalid_api_key The key is not one we issued. Check for truncation or whitespace; keys are sfs_ plus 32 letters and digits.
401 api_key_revoked The key was revoked. Make a new key at installgraph.com/account.
402 paid_plan_required Webhooks need an account that has bought a credit pack. Buy any pack: error.offers.pack.buyUrl. Then create the webhook again.
403 insufficient_scope The key lacks this endpoint's scope. Use a key with the scope the endpoint lists (GET /v1/usage shows yours).
403 api_key_suspended The key is suspended. Talk to us, quoting requestId.
404 not_found No such endpoint, app, or store (or not scanned yet). Check the slug or domain. Store 404s are free within the lookup-miss allowance.
409 webhook_limit_reached The account already has 25 active webhooks. Delete one with DELETE /v1/webhooks/{id}, or Talk to us: error.offers.talkToUs.url.

All codes: Errors.