{
  "openapi": "3.1.0",
  "info": {
    "title": "Envoke Hooks API",
    "version": "1.0.0",
    "description": "API for managing webhook subscriptions. Hooks deliver real-time HTTP POST payloads to your endpoint when contacts, form submissions, email activity, or lead events occur in your account. All endpoints require HTTP Basic Auth and return JSON."
  },
  "servers": [
    {
      "url": "https://e1.envoke.com/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "basicAuth": []
    }
  ],
  "tags": [
    {
      "name": "Hooks",
      "description": "Manage webhook subscriptions."
    }
  ],
  "components": {
    "securitySchemes": {
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "HTTP Basic Auth. Use your API ID as the username and API Key as the password."
      }
    },
    "schemas": {
      "HookType": {
        "type": "string",
        "description": "The event type that triggers the webhook delivery. `email_delivery` is reserved for an upcoming release: subscriptions are accepted, but no events are delivered yet.",
        "enum": [
          "new_contact",
          "consent_change",
          "submission",
          "form_submission",
          "preference_page_submission",
          "api_submission",
          "import_submission",
          "ui_submission",
          "email_send",
          "email_open",
          "email_click",
          "email_autoresponder_send",
          "email_bounce",
          "email_spam_report",
          "email_delivery",
          "new_lead",
          "passed_to_sales",
          "lead_update",
          "new_opportunity",
          "new_sale"
        ]
      },
      "HookFilter": {
        "type": "object",
        "description": "Optional key-value conditions evaluated against the event payload before delivery. All conditions must match. An empty object (`{}`) means no filter — every event of the subscribed type is delivered. Keys must be strings; values must be scalar (string, number, or boolean). Common filter keys: `form_id` for form events; `message_id` (integer message ID) or `message_external_id` for email events; `bounce_type` for `email_bounce`; `source` for `email_spam_report`. Keys are matched against the delivered payload, so a key the payload does not contain (for example `campaign_id`) never matches.",
        "additionalProperties": {
          "type": [
            "string",
            "number",
            "boolean"
          ]
        },
        "examples": [
          {},
          {
            "form_id": "abc123def456"
          },
          {
            "message_id": "789"
          },
          {
            "message_external_id": "f5a3c1e9b7d5f3a1c9e7b5d3f1a9c7e5"
          }
        ]
      },
      "Hook": {
        "type": "object",
        "description": "A webhook subscription.",
        "required": [
          "id",
          "type",
          "url",
          "filter"
        ],
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true,
            "description": "Unique 32-character hex identifier assigned on creation.",
            "examples": [
              "9f1c2a42baedb56c7e65b3eeaf1a8814"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/HookType"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The destination URL that receives the HTTP POST payload.",
            "examples": [
              "https://hooks.zapier.com/hooks/catch/123456/abcdef/"
            ]
          },
          "filter": {
            "$ref": "#/components/schemas/HookFilter"
          }
        }
      },
      "HookCreate": {
        "type": "object",
        "description": "Request body for creating a webhook subscription. `type` and `url` are required.",
        "required": [
          "type",
          "url"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/HookType"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The destination URL. Must start with http:// or https://.",
            "examples": [
              "https://hooks.zapier.com/hooks/catch/123456/abcdef/"
            ]
          },
          "filter": {
            "$ref": "#/components/schemas/HookFilter"
          }
        }
      },
      "CreateSuccessResponse": {
        "type": "object",
        "description": "Returned on successful webhook subscription creation (HTTP 201). Contains only the new subscription ID.",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The newly created subscription ID.",
            "examples": [
              "9f1c2a42baedb56c7e65b3eeaf1a8814"
            ]
          }
        }
      },
      "DeleteSuccessResponse": {
        "type": "object",
        "description": "Returned on deletion. Idempotent — returns success even if the ID did not exist.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string"
          },
          "result_data": {
            "type": "array",
            "maxItems": 0
          }
        }
      },
      "ValidationErrorResponse": {
        "type": "object",
        "description": "Returned when request body validation fails.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "result_text": {
            "type": "string",
            "const": "Validation failed, see 'result_data' -> 'errors'"
          },
          "result_data": {
            "type": "object",
            "required": [
              "errors"
            ],
            "properties": {
              "errors": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "One or more human-readable validation error messages."
              }
            }
          }
        }
      },
      "DuplicateErrorResponse": {
        "type": "object",
        "description": "Returned when a subscription with the same type and URL already exists.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "result_text": {
            "type": "string",
            "const": "A webhook subscription with this type and URL already exists"
          },
          "result_data": {
            "type": "object",
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "The ID of the existing subscription."
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "description": "Returned for general request errors.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "result_text": {
            "type": "string"
          },
          "result_data": {
            "type": "array",
            "maxItems": 0
          }
        }
      }
    },
    "parameters": {
      "HookId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The 32-character hex ID of the webhook subscription.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{32}$"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Maximum number of subscriptions to return. When omitted, all subscriptions are returned.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "Skip": {
        "name": "skip",
        "in": "query",
        "description": "Number of records to skip. Used with `limit` for pagination.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      },
      "Fields": {
        "name": "fields",
        "in": "query",
        "description": "Comma-separated list of fields to include in each response object.",
        "schema": {
          "type": "string"
        },
        "example": "id,type,url"
      },
      "TypeFilter": {
        "name": "type",
        "in": "query",
        "description": "Filter results to subscriptions matching the given event type.",
        "schema": {
          "$ref": "#/components/schemas/HookType"
        }
      }
    }
  },
  "paths": {
    "/hooks.json": {
      "get": {
        "summary": "List webhook subscriptions",
        "description": "Returns all webhook subscriptions for the authenticated account. All subscriptions are returned by default (no limit). Supports filtering by `type`, pagination, and field selection.",
        "operationId": "listHooks",
        "tags": [
          "Hooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Skip"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/TypeFilter"
          }
        ],
        "responses": {
          "200": {
            "description": "An array of webhook subscriptions. Returns `[]` if none exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Hook"
                  }
                },
                "examples": {
                  "list": {
                    "summary": "Two subscriptions",
                    "value": [
                      {
                        "id": "9f1c2a42baedb56c7e65b3eeaf1a8814",
                        "type": "form_submission",
                        "url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
                        "filter": {
                          "form_id": "abc123def456"
                        }
                      },
                      {
                        "id": "a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7",
                        "type": "email_open",
                        "url": "https://hooks.zapier.com/hooks/catch/123456/ghijkl/",
                        "filter": {
                          "message_id": "789"
                        }
                      }
                    ]
                  },
                  "filtered_by_type": {
                    "summary": "Filtered by type=form_submission",
                    "value": [
                      {
                        "id": "9f1c2a42baedb56c7e65b3eeaf1a8814",
                        "type": "form_submission",
                        "url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
                        "filter": {
                          "form_id": "abc123def456"
                        }
                      }
                    ]
                  },
                  "empty": {
                    "summary": "No subscriptions",
                    "value": []
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a webhook subscription",
        "description": "Creates a new webhook subscription. `type` and `url` are required. `filter` is optional — omit it or pass `{}` to receive all events of the given type.\n\nReturns HTTP 201 on success. Returns HTTP 400 with a duplicate error if a subscription with the same `type` and `url` already exists.",
        "operationId": "createHook",
        "tags": [
          "Hooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HookCreate"
              },
              "examples": {
                "form_submission_with_filter": {
                  "summary": "Form submission — specific form (college admissions inquiry)",
                  "value": {
                    "type": "form_submission",
                    "url": "https://crm.ridgeline.edu/webhooks/envoke/admissions",
                    "filter": {
                      "form_id": "abc123def456"
                    }
                  }
                },
                "email_open_with_filter": {
                  "summary": "Email open — specific message by ID (fall registration reminder)",
                  "value": {
                    "type": "email_open",
                    "url": "https://portal.ridgeline.edu/webhooks/engagement",
                    "filter": {
                      "message_id": "789"
                    }
                  }
                },
                "email_click_with_filter": {
                  "summary": "Email click — specific message by external ID (alumni giving appeal)",
                  "value": {
                    "type": "email_click",
                    "url": "https://giving.ridgeline.edu/webhooks/envoke",
                    "filter": {
                      "message_external_id": "f5a3c1e9b7d5f3a1c9e7b5d3f1a9c7e5"
                    }
                  }
                },
                "email_bounce_hard_only": {
                  "summary": "Email bounce — hard bounces only (CRM address hygiene)",
                  "value": {
                    "type": "email_bounce",
                    "url": "https://crm.ridgeline.edu/webhooks/envoke/bounces",
                    "filter": {
                      "bounce_type": "hard"
                    }
                  }
                },
                "email_spam_report_no_filter": {
                  "summary": "Email spam report — all reports",
                  "value": {
                    "type": "email_spam_report",
                    "url": "https://crm.ridgeline.edu/webhooks/envoke/complaints",
                    "filter": {}
                  }
                },
                "new_contact_no_filter": {
                  "summary": "New contact — all new contacts (sync to student info system)",
                  "value": {
                    "type": "new_contact",
                    "url": "https://sis.ridgeline.edu/webhooks/contacts",
                    "filter": {}
                  }
                },
                "new_lead_no_filter": {
                  "summary": "New lead — all new leads (alert recruitment team)",
                  "value": {
                    "type": "new_lead",
                    "url": "https://crm.ridgeline.edu/webhooks/leads"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscription created successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateSuccessResponse"
                },
                "example": {
                  "id": "9f1c2a42baedb56c7e65b3eeaf1a8814"
                }
              }
            }
          },
          "400": {
            "description": "Validation error or duplicate subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ValidationErrorResponse"
                    },
                    {
                      "$ref": "#/components/schemas/DuplicateErrorResponse"
                    }
                  ]
                },
                "examples": {
                  "missing_type": {
                    "summary": "Missing required field: type",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "'type' is required"
                        ]
                      }
                    }
                  },
                  "invalid_type": {
                    "summary": "Unrecognised event type",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid webhook type: 'contact_created'"
                        ]
                      }
                    }
                  },
                  "missing_url": {
                    "summary": "Missing required field: url",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "'url' is required"
                        ]
                      }
                    }
                  },
                  "invalid_url": {
                    "summary": "URL does not start with http:// or https://",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "'url' must start with http:// or https://"
                        ]
                      }
                    }
                  },
                  "invalid_filter": {
                    "summary": "Filter value is not a scalar",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "'filter' values must be scalar (string, number, or boolean)"
                        ]
                      }
                    }
                  },
                  "duplicate": {
                    "summary": "Duplicate subscription",
                    "value": {
                      "success": false,
                      "result_text": "A webhook subscription with this type and URL already exists",
                      "result_data": {
                        "id": "9f1c2a42baedb56c7e65b3eeaf1a8814"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/hooks/{id}.json": {
      "get": {
        "summary": "Get a single webhook subscription",
        "description": "Returns a single webhook subscription by its ID. Returns `null` if the ID is not found.",
        "operationId": "getHook",
        "tags": [
          "Hooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/HookId"
          }
        ],
        "responses": {
          "200": {
            "description": "The webhook subscription, or `null` if not found.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Hook"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "examples": {
                  "found": {
                    "summary": "Subscription found",
                    "value": {
                      "id": "9f1c2a42baedb56c7e65b3eeaf1a8814",
                      "type": "form_submission",
                      "url": "https://crm.ridgeline.edu/webhooks/envoke/admissions",
                      "filter": {
                        "form_id": "abc123def456"
                      }
                    }
                  },
                  "not_found": {
                    "summary": "Subscription not found",
                    "value": null
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a webhook subscription",
        "description": "Permanently deletes a webhook subscription. No further events are delivered once deleted. The operation is idempotent — deleting a non-existent ID returns a success response.",
        "operationId": "deleteHook",
        "tags": [
          "Hooks"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/HookId"
          }
        ],
        "responses": {
          "200": {
            "description": "Subscription deleted (or did not exist — deletion is idempotent).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Deleted",
                  "result_data": []
                }
              }
            }
          }
        }
      }
    }
  }
}
