← Files YCloud Developer KitARCHIVED FILE

skills/ycloud-webhook-endpoints/references/runtime.md

16.7 KB · Oct 5, 2026 · 18:33 UTC

↓ Download file

<!-- Generated by scripts/build_public_docs_references.py; do not edit. -->
# Official runtime behavior

- Evidence retrieved: `2026-08-29`
- Scope: curated public YCloud documentation facts; no live API observation
- Precedence: exact operation path/schema stays with `openapi.md`; this file supplies cross-cutting runtime behavior. Conflicts fail closed.

## Sources

- [api-examples-overview](https://newdocs.ycloud.com/en/api-reference/guides/examples/overview) — observed update label: retrieve at use time; no cached payload
- [errors](https://docs.ycloud.com/reference/errors) — observed update label: over 1 year ago
- [rate-limits](https://docs.ycloud.com/reference/rate-limits) — observed update label: 3 months ago
- [request-ids](https://docs.ycloud.com/reference/request-ids) — observed update label: over 1 year ago
- [versioning](https://docs.ycloud.com/reference/versioning) — observed update label: about 1 year ago
- [webhook-guide-v2](https://newdocs.ycloud.com/en/guides/webhooks) — observed update label: modified 2026-07-16
- [message-status](https://docs.ycloud.com/reference/whatsapp-message-updated-webhook-examples) — observed update label: 4 months ago
- [pagination](https://docs.ycloud.com/reference/pagination) — observed update label: about 1 year ago
- [webhook-integration-guide](https://docs.ycloud.com/reference/webhook-integration-guide) — observed update label: 8 months ago
- [webhooks](https://docs.ycloud.com/reference/configure-webhooks) — observed update label: over 1 year ago

## Required live documentation lookup

- Before constructing or judging a provider-valid API request or response example, browse the latest official [API examples overview](https://newdocs.ycloud.com/en/api-reference/guides/examples/overview) and the relevant example page it links to.
- Before constructing or judging an event-specific Webhook payload, browse the latest official [Webhook guide](https://newdocs.ycloud.com/en/guides/webhooks) and its relevant linked example.
- The pinned `openapi.md` controls exact paths, methods, parameters, and structural schemas. Current official examples control demonstrated cross-field combinations. If they conflict, stop, report contract drift, and do not guess or merge the shapes.
- If live documentation cannot be reached, a model may draft only a best-effort `synthetic_unverified` fixture from the pinned contract. Label it explicitly, keep it out of live transport, and never claim it is provider-valid or that YCloud was reached.

### Test evidence levels

- Deterministic unit and pull-request tests must use an injected mock transport and synthetic data. They prove local request/response mapping and failure handling, not provider connectivity.
- Provider-shaped sandbox tests prove the local HTTP boundary and controlled lifecycle/failure scenarios, not production YCloud behavior.
- Claim `live_connected` only after a separate, explicitly authorized live smoke suite reaches YCloud with dedicated test resources and records sanitized request-ID/status evidence. Live writes must never run as the default test command or an ordinary pull-request gate.

### Live transport reachability evidence

- Entering a live transport, constructing a URL, or calling `fetch` proves only `request_prepared` or `request_attempted`; it does not prove the request reached YCloud.
- If no HTTP response headers were received, no YCloud error envelope was received, and no `YCloud-Request-ID` was received, report `provider_reachability_unconfirmed`. Do not say the request was sent to, received by, rejected by, or timed out at YCloud.
- A locally raised validation/error-handler response is project evidence, not a provider response. DNS, TLS, proxy, connect, socket, abort, and client-deadline failures must never be rewritten as provider HTTP 400 or represented as a YCloud error envelope.
- Map a confirmed local client deadline with no provider response to gateway timeout semantics (`504`). Map other pre-response upstream connectivity failures to bad-gateway semantics (`502`). Keep mutation outcomes ambiguous whenever the provider may have accepted bytes before the response was lost; require reconciliation before replay.
- Record a sanitized outbound attempt with `attemptedUrl`, `method`, `startedAt`, elapsed duration, best-known phase (`dns`, `tls`, `proxy`, `connect`, `request_write`, `response_headers`, `response_body`, `timeout`, or `unknown`), whether response headers were received, and `YCloud-Request-ID` only when actually present. Never record `X-API-Key`, Authorization values, secrets, sensitive query values, or request bodies by default.
- Runtime libraries such as `fetch` may not expose DNS/TLS/connect timing separately. Classify a precise phase only from supported telemetry or a concrete nested error code; otherwise preserve `unknown` instead of guessing.
- Receiving an HTTP response establishes that an upstream HTTP peer responded. A genuine `YCloud-Request-ID` is the preferred YCloud correlation evidence. Absence of that header does not authorize inventing a placeholder provider request ID.

## Confirmed facts

- A successful API request uses a 2xx status; documented failures use 4xx for client-caused errors and 5xx for YCloud server errors.
- The standard failure envelope is {error:{status,code,message?,target?,docUrl?,requestId?,whatsappApiError?}}. status and code are required; message is diagnostic text and must not be exposed directly to end users.
- YCloud-Request-ID is returned as a response header and is also mirrored by error.requestId when present; preserve it for correlation and support without logging credentials or customer payloads.
- Throttling is reported with HTTP 429. Retry-After is a delay in seconds; when present on any response, wait before a new request instead of treating it as a successful-delivery signal.
- RateLimit-Limit, RateLimit-Policy, RateLimit-Remaining, and RateLimit-Reset may be returned. The published header format is beta and based on an IETF draft, so parse defensively and do not make it a required response shape.
- The Management API table and example header publish 10000 requests per hour, while one explanatory sentence on the same page says 1000. Treat the table value as documented guidance, prefer runtime headers, and report source drift instead of hard-coding the conflicting prose.
- Documented 429 codes include ACCOUNT_RATE_LIMITED, SENDER_RATE_LIMITED, and TOO_MANY_REQUESTS; branch on the received HTTP status and error.code rather than error.message text.
- YCloud treats added optional request parameters, response properties, resources, enum values, and changes to opaque-string formats as backward-compatible. Clients must ignore unknown response properties and preserve an explicit unknown branch for enum or event-type values instead of failing exhaustive decoding.
- Treat YCloud-generated IDs as opaque, case-sensitive strings. Do not parse fixed prefixes; support values up to 255 characters as documented by the versioning policy.
- Webhook endpoint management shares the documented Management API policy: 200 requests per second and 10000 requests per hour per account across the shared management quota.
- A receiver should quickly return a 2xx response before complex processing. If YCloud does not quickly receive 2xx, the documented retry intervals are 10s, 30s, 5m, 30m, 1h, 2h, and 2h, up to 7 retries; after those retries YCloud does not retry that event again.
- YCloud-Signature has the form t=<unix-seconds>,s=<64 lowercase hex HMAC-SHA256>. Using the endpoint secret as key, sign ASCII decimal timestamp, one period byte, and the exact raw request body bytes in that order. There is no trailing delimiter.
- Parse t and s from comma-separated key/value elements, compare signatures using a constant-time facility, and apply an application-selected timestamp tolerance. The docs require a tolerance check but do not prescribe its numeric value.
- Verify the unmodified raw request bytes before parsing or JSON reserialization, then acknowledge and process asynchronously. Whitespace, Unicode encoding, property order, and terminal newlines are signed data. Treat the event id as a deduplication key because delivery is retried; the retention duration is an application decision, not a YCloud guarantee.
- whatsapp.message.updated notifications are not guaranteed to arrive in order and may be duplicated; a later delivered event can appear around failed events, so consumers must use idempotent state handling rather than append-only assumptions.
- The canonical WhatsApp event names are dotted: whatsapp.inbound_message.received and whatsapp.message.updated. Legacy underscore aliases and singular update variants are not subscription event names.
- Webhook receivers must be publicly reachable and must not use a private/internal IP; HTTPS is strongly recommended. An account can configure at most 20 webhook endpoints, with URL length up to 500 characters and description length up to 400 characters.
- Incoming requests include Content-Type: application/json, YCloud-Signature, and X-Webhook-Endpoint-ID. Preserve the endpoint ID as non-secret routing/correlation metadata while keeping the endpoint secret redacted.
- Return a 2xx quickly (within 6 seconds is recommended; responses slower than 10 seconds may be deprioritized), then process asynchronously. The response body is ignored.
- A URL can be suspended after 200 failures per minute or 10 minutes of cumulative failure time per minute. Suspension lasts 3 minutes, sends no webhooks during that interval, and resumes automatically; the public contract does not promise backfill for events created during suspension.
- Treat new webhook event types and response properties as backward-compatible additions. Verify the common envelope, acknowledge safely, and route unfamiliar types to an observable unsupported-event path instead of rejecting the entire receiver.
- Webhook endpoint list uses offset pagination: page is 1-based, limit is 1 through 100 with default 10, and total is returned only when includeTotal=true.

## Still fail closed

- The public cross-cutting pages do not define a general client idempotency key or guarantee that replaying a mutating request is safe.
- Do not invent endpoint-specific errors, undocumented retry counts, or a generic retry policy when the selected operation/reference does not provide one.
- The public page does not define a universal deduplication TTL, exact timeout cutoff, fixed timestamp tolerance, dual-secret rotation window, or rollback behavior.

## Machine-readable failure decision contract

- Contract: `failure-contract-v1` (`failure-contract.json`)
- Authority: provider evidence, Developer Kit policy, and project decisions remain separate. A derived retry decision is not a YCloud response field.
- Provider envelope requires `status, code` and may include `status, code, message, target, docUrl, requestId, whatsappApiError`. It does not define `retryable`; do not parse `message` for control flow.
- Automatic retry requires a transient failure, a replay-safe operation, an allowed project retry budget, and satisfaction of Retry-After. Failure transience alone never authorizes replay.
- Unknown YCloud or Meta codes preserve the original value and fail closed with `automaticRetry=false`.
- No general provider idempotency key or mutation replay guarantee is documented. `externalId` is application correlation and grants no replay safety. A project-owned key must define scope, durable command identity, request digest conflicts, retention/TTL, duplicate in-progress/completed results, and ambiguous-outcome reconciliation.
- Stable decisions: `correct_request, reauthenticate, reauthorize, reconcile, wait, retry_if_replay_safe, await_async_status, do_not_retry, escalate, unknown`. Transport timeout, connection loss, or a lost response produces `ambiguous_outcome` with automatic retry disabled and reconciliation required.

### Decision matrix

| Signal | Safe read | Mutation | Authority |
| --- | --- | --- | --- |
| `HTTP_400` | `correct_request` | `correct_request` | `developer_kit_policy` |
| `HTTP_401` | `reauthenticate` | `reauthenticate_then_reconcile_before_new_attempt` | `developer_kit_policy` |
| `HTTP_403` | `reauthorize_or_fix_precondition` | `reauthorize_or_fix_precondition` | `developer_kit_policy` |
| `HTTP_404` | `return_not_found` | `reconcile_if_previous_outcome_was_ambiguous` | `developer_kit_policy` |
| `HTTP_409` | `reconcile` | `reconcile_without_blind_replay` | `developer_kit_policy` |
| `HTTP_429` | `honor_retry_after_then_bounded_retry` | `honor_retry_after_for_new_traffic_do_not_replay` | `developer_kit_policy` |
| `HTTP_5XX_OR_503` | `bounded_retry_if_budget_allows` | `reconcile_ambiguous_outcome` | `developer_kit_policy` |
| `TIMEOUT_CONNECTION_LOSS_NO_RESPONSE` | `bounded_retry_if_budget_allows` | `ambiguous_outcome_reconcile_before_new_attempt` | `developer_kit_policy` |
| `QUEUED_ACCEPTED` | `not_applicable` | `await_retrieve_or_verified_message_updated_event` | `confirmed_provider_contract` |
| `UNKNOWN_CODE_OR_STATUS` | `preserve_and_fail_closed` | `preserve_and_fail_closed_no_replay` | `developer_kit_policy` |

### YCloud error-code catalog

| Code | HTTP | Class | Transience | Action |
| --- | ---: | --- | --- | --- |
| `ACCOUNT_LIMITED` | `403` | `authorization` | `conditional` | `reauthorize` |
| `ACCOUNT_RATE_LIMITED` | `429` | `rate_limit` | `transient` | `wait` |
| `ACCOUNT_UNAVAILABLE` | `403` | `availability` | `conditional` | `escalate` |
| `ALREADY_EXISTS` | `409` | `conflict` | `non_transient` | `reconcile` |
| `BAD_REQUEST` | `400` | `validation` | `non_transient` | `correct_request` |
| `BALANCE_INSUFFICIENT` | `403` | `precondition` | `conditional` | `do_not_retry` |
| `CONTENT_PROHIBITED` | `403` | `policy` | `non_transient` | `do_not_retry` |
| `CONTENT_TOO_LARGE` | `413` | `validation` | `non_transient` | `correct_request` |
| `EMAIL_DOMAIN_UNVERIFIED` | `403` | `precondition` | `conditional` | `wait` |
| `FORBIDDEN` | `403` | `authorization` | `non_transient` | `reauthorize` |
| `INTERNAL_SERVER_ERROR` | `500` | `server` | `transient` | `retry_if_replay_safe` |
| `MESSAGING_REGION_UNSUPPORTED` | `400` | `validation` | `non_transient` | `correct_request` |
| `NOT_FOUND` | `404` | `not_found` | `non_transient` | `reconcile` |
| `PARAM_INVALID` | `400` | `validation` | `non_transient` | `correct_request` |
| `PARAM_INVALID_LENGTH` | `400` | `validation` | `non_transient` | `correct_request` |
| `PARAM_MISSING` | `400` | `validation` | `non_transient` | `correct_request` |
| `PARAM_NOT_MATCH` | `400` | `validation` | `non_transient` | `correct_request` |
| `RECIPIENT_IN_BLOCK_LIST` | `403` | `recipient_policy` | `non_transient` | `do_not_retry` |
| `RECIPIENT_UNSUBSCRIBED` | `403` | `recipient_policy` | `non_transient` | `do_not_retry` |
| `SENDER_ID_UNAVAILABLE` | `403` | `precondition` | `conditional` | `do_not_retry` |
| `SENDER_RATE_LIMITED` | `429` | `rate_limit` | `transient` | `wait` |
| `SERVICE_UNAVAILABLE` | `503` | `server` | `transient` | `retry_if_replay_safe` |
| `SMS_SIGNATURE_UNAVAILABLE` | `403` | `precondition` | `conditional` | `do_not_retry` |
| `TOO_MANY_REQUESTS` | `429` | `rate_limit` | `transient` | `wait` |
| `UNAUTHORIZED` | `401` | `authentication` | `non_transient` | `reauthenticate` |
| `WHATSAPP_BUSINESS_ACCOUNT_UNAVAILABLE` | `403` | `authorization` | `conditional` | `reauthorize` |
| `WHATSAPP_DIRECT_SEND_UNSUPPORTED_COMPONENT` | `400` | `validation` | `non_transient` | `correct_request` |
| `WHATSAPP_PHONE_NUMBER_UNAVAILABLE` | `403` | `precondition` | `conditional` | `do_not_retry` |
| `WHATSAPP_TEMPLATE_UNAVAILABLE` | `403` | `precondition` | `conditional` | `do_not_retry` |
| `WHATSAPP_TEMPLATE_UNEDITABLE` | `403` | `precondition` | `non_transient` | `correct_request` |
| `WHATSAPP_WABA_UNAVAILABLE` | `403` | `authorization` | `conditional` | `reauthorize` |

### Operation replay profiles (6)

| Operation | Method | Side effect | Result channel | Replay safety | Auto retry | OpenAPI failure gap |
| --- | --- | --- | --- | --- | --- | --- |
| `webhook_endpoint-create` | `POST` | `create` | `synchronous_acceptance_or_result` | `not_confirmed` | `never` | `true` |
| `webhook_endpoint-delete` | `DELETE` | `delete` | `synchronous_acceptance_or_result` | `not_confirmed` | `never` | `false` |
| `webhook_endpoint-list` | `GET` | `read` | `synchronous_read` | `replay_safe_read` | `conditional` | `true` |
| `webhook_endpoint-retrieve` | `GET` | `read` | `synchronous_read` | `replay_safe_read` | `conditional` | `false` |
| `webhook_endpoint-rotate-secret` | `POST` | `rotate` | `synchronous_acceptance_or_result` | `not_confirmed` | `never` | `false` |
| `webhook_endpoint-update` | `PATCH` | `update` | `synchronous_acceptance_or_result` | `not_confirmed` | `never` | `false` |

The profile is conservative Kit policy. A mutation with a timeout, connection loss, or lost response is an ambiguous outcome: reconcile, retrieve, or await the documented asynchronous status before any new attempt.

SHA-256: 7e30e75768123aacd597e6a0ae59f0d8d15372c30b7075a90d2fb1c1a1e2bea7