Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.zapier.com/llms.txt

Use this file to discover all available pages before exploring further.

Instead of polling GET /v2/action-runs/{id} for every result, you can include a callback_url in your request. When the run reaches a terminal state, Zapier POSTs the result directly to your endpoint. This is the recommended approach at high volumes — polling creates one request per run, while callbacks let Zapier push results to you.

Adding a Callback URL

Include the optional callback_url field in your POST /v2/action-runs request:
POST https://api.zapier.com/v2/action-runs/
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "action": "core:89sg4uhs5g85gh53hso59hs399hgs59",
  "authentication": "UHsi8e6K",
  "input": {
    "email": "user@example.com",
    "message": "Hello from Powered by Zapier!"
  },
  "callback_url": "https://your-app.example.com/zapier/callbacks"
}
The response is unchanged — you receive a run ID immediately while Zapier executes the action asynchronously:
{
  "data": {
    "type": "run",
    "id": "arun_abc123"
  }
}

Validation Rules

The callback_url must meet these requirements, or the request returns 400 Bad Request:
RuleRequirement
ProtocolHTTPS only
IP rangesPublic IPs only — private ranges (10.x, 172.16–31.x, 192.168.x, 127.x, ::1) are rejected
Max length2048 characters

Callback Payload

When the action reaches a terminal state, Zapier sends a POST to your callback_url. Your endpoint must return a 2xx response to acknowledge receipt.

Success

POST https://your-app.example.com/zapier/callbacks
Content-Type: application/json
Zapier-Callback-Signature: <signed JWT>

{
  "data": {
    "type": "run",
    "id": "arun_abc123",
    "status": "success",
    "results": [
      { ... }
    ],
    "errors": []
  }
}
The id in the payload matches the run ID returned by the original POST /v2/action-runs response. You can use the same callback_url for every action run and dispatch on id to correlate each callback with its originating request.

Error

POST https://your-app.example.com/zapier/callbacks
Content-Type: application/json
Zapier-Callback-Signature: <signed JWT>

{
  "data": {
    "type": "run",
    "id": "arun_abc123",
    "status": "error",
    "results": [],
    "errors": [
      {
        "code": "user",
        "title": "Record not found",
        "detail": "The specified record could not be found in the target app."
      }
    ]
  }
}

Security

Verifying Callback Authenticity

Each callback includes a Zapier-Callback-Signature header containing a JWT signed with Zapier’s private key. Verify it using Zapier’s public keys from our JWKS endpoint:
https://zapier.com/.well-known/jwks.json
import jwt                         # pip install PyJWT
import requests
from jwt.algorithms import RSAAlgorithm
from functools import lru_cache

JWKS_URL = "https://zapier.com/.well-known/jwks.json"

@lru_cache(maxsize=1)
def _get_jwks():
    return requests.get(JWKS_URL).json()["keys"]

def get_public_key(kid):
    for key in _get_jwks():
        if key["kid"] == kid:
            return RSAAlgorithm.from_jwk(key)
    raise ValueError(f"Unknown key ID: {kid}")

def verify_zapier_callback(headers):
    token = headers.get("Zapier-Callback-Signature")
    if not token:
        raise ValueError("Missing Zapier-Callback-Signature header")

    header = jwt.get_unverified_header(token)
    public_key = get_public_key(header["kid"])
    # PyJWT validates exp automatically — expired tokens raise ExpiredSignatureError
    return jwt.decode(token, public_key, algorithms=["RS256"])

Replay Protection

The JWT includes iat and exp claims. Both PyJWT and jose validate exp automatically and will throw on an expired token — no manual timestamp check required. Callbacks are valid for 5 minutes from issuance.

Reliability

Retry Behavior

Zapier retries on 5xx responses and network errors using exponential backoff, up to 3 total attempts. Retries happen quickly (within seconds). 4xx responses are not retried. If your endpoint returns a 4xx, the callback is immediately marked failed — fix the endpoint issue and fall back to polling to retrieve the result. After all retries are exhausted, the callback is marked failed. You can still retrieve the result by polling GET /v2/action-runs/{id}.

Idempotency

Network issues may occasionally cause your endpoint to receive the same callback more than once. Deduplicate on the run id in the payload body.

Failure Modes

ScenarioWhat happensWhat to do
Endpoint returns 5xxRetried with exponential backoff (up to 3 attempts)Fix the endpoint; check your server logs
Endpoint returns 4xxNot retried; callback permanently failedFix the endpoint error, then poll GET /v2/action-runs/{id} for the result
Endpoint unreachableRetries exhaust; callback marked failedPoll GET /v2/action-runs/{id} as fallback
Duplicate callback receivedCan occur if your endpoint times out after returning 2xxDeduplicate on the run id in the payload body
Long-running action (>45s)Callback fires when the action eventually completesNo action needed — just wait
Invalid callback_url on POST400 Bad Request with a validation errorUse a valid HTTPS URL on a public IP

Fallback: Polling

Callbacks are best-effort. If your endpoint is unavailable and retries are exhausted, fall back to polling:
GET https://api.zapier.com/v2/action-runs/arun_abc123/
Authorization: Bearer YOUR_ACCESS_TOKEN
See Retrieving Action Run Results for full details.