HOC SiteDocumentation

Integrations

Outbound integrations are packages of secrets, queries, and inbound webhooks. This is separate from Web integrations, which only store analytics and verification tags. Secrets are write-only: reads return metadata, never the secret value. Sandbox versus live is the package baseUrl plus two secret names, not an environment column.

List catalog templates you can install and packages already on the website.

GET /api/v1/{locale}/{websiteId}/integrations?resource=packages

Install a catalog template. Pass templateId or slug. The response includes packageId. Secret values are not copied.

POST /api/v1/{locale}/{websiteId}/integrations
{
  "resource": "packages",
  "action": "install",
  "slug": "iyzico-sandbox"
}

Read one installed package, including variables. Each variable is {id, name, value}. You supply the id. baseUrl is one variable. Sandbox versus live is that value plus two secret names, not an environment column.

GET /api/v1/{locale}/{websiteId}/integrations?resource=packages&id={packageId}

Create your own package with action create, or change one with action update and packageId. Sending variables replaces that list. Omitted update fields stay. An empty name is invalid_input at path name. Update sends the same endpoint with action update, packageId, and input.variables.

POST /api/v1/{locale}/{websiteId}/integrations
{
  "resource": "packages",
  "action": "create",
  "input": {
    "name": "Checkout",
    "variables": [
      {
        "id": "11111111-1111-7111-8111-111111111111",
        "name": "baseUrl",
        "value": "https://sandbox-api.iyzipay.com"
      }
    ]
  }
}

MCP: get_integration_packages (no packageId lists them), install_integration_package, save_integration_package, get_integration_secrets, save_integration_secret, rotate_integration_secret, delete_integration_secret, get_integration_queries (no queryId lists them), save_integration_query, get_integration_webhooks (no webhookId lists them), save_integration_webhook, delete_integration_webhook.

Secrets

Create, rotate, or delete a secret with resource secrets. A missing or guessed packageId on query or webhook create returns invalid_input with path packageId. Save a query or webhook with resource queries or webhooks, action save, and packageId when creating. secretSlotRefs stores the secret id returned here, never the secret name.

Queries

A query definition includes request.delivery: queue (default) or await. queue runs after the caller returns. await finishes before the data-request or lifecycle response, so a response handler can write a field such as payment_page_url onto the entry first.

request.bodyAst is an ordered JSON tree. Node kinds are null, boolean, number, string, binding, each, object, and array. A text value is {"kind":"string","value":"tr"}. {"kind":"literal"} and {"kind":"var"} are not body nodes. A whole value taken from a binding is {"kind":"binding","id":"<binding uuid>"}. An object node uses entries: [{"key":"locale","value":<node>}].

bindings is an array of {id, label, kind, ref}. kind is package_var, field, var, secret_slot, relation_one, or each. package_var ref is the package variable id. var ref is the mapping variable name. Use the same id in urlTemplate as {{binding:<id>}}.

secretSlotRefs maps an auth slot name to a secret id from create_integration_secret. Example: {"apiKey":"<secret uuid>"}.

mapping.steps set_var is where kind literal, field, system, var, and webhook belong. Example: {"type":"set_var","name":"locale","source":{"kind":"literal","value":"tr"}}. field and var sources use path.

responseHandlers write the call result back. Each handler has id, name, enabled, constraints, and actions. modify_field copies a response path onto an entry field: {"type":"modify_field","fieldKey":"payment_page_url","source":{"kind":"path","path":"response.body.paymentPageUrl"}}. Payment status uses response.body.paymentStatus. When paymentStatus is empty and response.body.status is failure, a second handler can write the literal FAILURE.

components[] is { id, enabled?, relationPath? | inverse?, itemAst? }. enabled defaults to false, so a component that is omitted or false is skipped and the each node emits nothing. Set relationPath (a forward to-many field on the source entry) or inverse { objectTypeSlug, viaField } for entries of that type whose viaField points at this entry. itemAst is the JSON body for one related row. The body node is {"kind":"each","componentId":"<components[].id>"}, and it expands to the row array. An each node that is an item of an array is spliced into that array, so a basketItems array whose only item is each is a flat list. A binding inside itemAst is {"kind":"binding","id":"<bindings[].id>"}. The field path belongs on bindings[].ref, not on the binding id. kind field or relation_one with ref line_total reads that field on the row. ref id reads the row entry id. A dotted ref such as product.name walks relations from the row and reads that entry in the run locale. kind var stays the parent mapping variable inside the row. kind each is not a row value. Object type slugs may include hyphens (order-lines) as well as underscores. Field keys stay snake_case. get_integration_run (no runId lists them) returns each run's sanitized request and upstream response for a user who can define integrations. A public page shows the upstream error and the run id, not the outbound body.

A dotted field or relation_one ref used in the parent body is checked against the query source type (source.objectTypeSlug or source.objectTypeId). The same ref used only inside an inverse itemAst is checked against that inverse object type. A hop that is not a relation, a binding id that is not in bindings, an each componentId that is not in request.components, or an inverse objectTypeSlug that is not a type on the website returns 400 on save. Lifecycle action update_matching_entries updates existing rows of objectTypeSlug. where is 1–3 clauses { fieldKey, op, valueFrom?, value?, field? }. op is eq, neq, empty, not_empty, lt, or gt. empty and not_empty omit the right side. Otherwise valueFrom is literal (value) or field (a key on the triggering entry). set is an array of 1–8 items. Each item is { fieldKey, valueFrom: "literal", value } or { fieldKey, valueFrom: "self" }. An object for set returns 400 invalid_input with a path pointing at set. self writes a relation to the triggering entry even when visitors cannot write that field. The match is capped. After the action, calculated fields on the triggering entry are recomputed before later actions in the same event, so run_integration_query sees the new sum.

An invalid query returns 400 invalid_input with path, received, and hint.

Webhooks

An inbound webhook sets bodyFormat to json, xml, or form. form parses application/x-www-form-urlencoded. target is an optional legacy field. handlers declare the behavior, so a form callback with no target is valid. An invalid target object returns 400 invalid_input and nothing is written.

publicPath from save and get is the absolute callback URL. A site with no custom domain uses https://hoc-site.com/api/integrations/{websiteId}/{urlName}. A custom domain uses https://{domain}/api/integrations/{urlName}. Do not put the website id in front of /api on a page path. That URL is a page, not this callback.

handlers is an array of {id, name, enabled, constraints, actions}. update_entry needs objectTypeId or objectTypeSlug, matchKeyPath, and fields. run_integration_query needs queryId or queryName. redirect uses successUrl, failureUrl, statusPath (default entry.payment_status), successValue (default SUCCESS), optional query, and optional localePath. query is up to 8 { name, path } entries. path is an integration path such as entry.id or entry.payment_error and is appended to the public URL. localePath, such as locale, chooses that public locale instead of the website primary. A relative redirect is prefixed with the website public path. A checkout callback can update the order, run the retrieve query, then redirect.

Delete with resource webhooks, action delete, and webhookId.

Idempotency stays on idempotency.keyParts, such as {"kind":"jsonPath","path":"token"}. The path is read from the parsed body, so token, body.token, and webhook.body.token all match a form field named token. The first POST for a key runs the handlers. A second POST for that same key, while the first is still running, returns 409 in_flight. A later duplicate replays the stored redirect as 303. If that run stored no redirect, the body is 200 {"ok":true,"duplicate":true}.

Handler field sources are literal or path. The webhook body root is webhook.body (webhook.body.token). There is no body root, and kind webhook is only valid on mapping set_var. A path that does not resolve is omitted and does not clear the stored field. Written values are checked against the field schema, including enum options.

successUrl and failureUrl are a site path starting with / or an http(s) URL on this website or the platform origin. Anything else is 400 invalid_input on save. The Location uses the public site origin (https://hoc-site.com for a path-prefix callback, or the custom domain), not the internal server host. A relative path is prefixed with the locale from localePath when that path resolves, otherwise the website primary locale.

An invalid webhook returns 400 invalid_input with a path.