Runtime API

Use, protect, rotate, and scope dh_live_ keys for Dualhook's allowlisted Graph-compatible WhatsApp runtime.

Graph-Compatible Runtime

The Runtime API is a deliberately allowlisted subset of Meta's WhatsApp Cloud API and WhatsApp Business Management API. For supported operations, migrate an existing Graph request by changing:

- https://graph.facebook.com/v25.0/...
+ https://api.dualhook.com/v25.0/...

- Authorization: Bearer <META_ACCESS_TOKEN>
+ Authorization: Bearer <DH_LIVE_KEY>

Keep the supported Meta path, method, query, and JSON shape. Dualhook is not an unrestricted Graph API proxy: only the operations in the Runtime API Endpoint Reference are accepted. When a request fails, Runtime API Errors works back from the response to the fix.

Connection-Scoped Keys

Each dh_live_... key is bound to exactly one Dualhook connection, organization, phone-number ID, and WABA ID. It cannot be used for another connection or as a general Meta credential.

Create and Store a Key

An organization admin creates the key from Connection → Overview → Outbound API key. Plaintext is displayed once. Dualhook stores only its SHA-256 hash.

Store the key in a server-side secret manager. Never put it in browser code, mobile applications, public automation exports, logs, screenshots, or support messages. Do not share, resell, sublicense, or reuse it for another customer.

Authorization: Bearer dh_live_...

Authentication Boundary

Customers submit only the dh_live_... credential. Never submit any of these values to the Runtime API:

  • Meta access or app access tokens
  • Meta App Secret or app ID
  • appsecret_proof
  • a second Authorization value
  • business, WABA, or phone IDs in a request body
  • Graph hosts or webhook/callback URLs

Dualhook loads the selected connection's encrypted Meta authorization inside the Worker and applies Meta App Secret Proof internally to every authenticated Graph request. Caller credentials, proof, hosts, or asset overrides are rejected rather than forwarded.

Temporary media-download URLs are the documented exception: after a proof-aware media metadata request, the Worker downloads bytes from a validated Facebook/CDN hostname using Meta's Bearer-only media-download flow. Redirects remain manual.

Rotation

Rotation creates a new primary key and offers three choices for the old key:

  • revoke immediately
  • keep a one-hour grace overlap
  • keep a 24-hour grace overlap

Use an overlap only long enough to deploy the new key.

Routing Holds and Revocation

A reversible billing, plan-limit, administrator, or Meta routing hold does not revoke the connection's runtime key. The key remains bound to the same connection but cannot route requests while the connection is blocked. If the final hold is released and Meta webhook routing is restored and verified, the same key works again.

Keys are invalidated by terminal connection removal, explicit key revocation or rotation, expiration of a grace key, or an administrator quarantining that credential after a security incident. Creating or rotating a key is unavailable while its connection is not routable.

connection_not_routable

A valid key receives HTTP 403 when its connection cannot currently route requests:

{
  "error": {
    "code": "connection_not_routable",
    "reason": "billing_unpaid",
    "message": "The subscription does not currently permit API routing.",
    "docs": "https://dualhook.com/docs/runtime-api#connection_not_routable"
  }
}

The reason is restricted to this public allowlist:

ReasonMeaning
onboarding_incompleteConnection setup is incomplete.
plan_limit_graceA temporary plan-limit grace period applies.
plan_limit_exceededThe subscription's connection limit was exceeded.
billing_unpaidThe subscription does not currently permit routing.
subscription_canceledThe subscription was canceled.
admin_hold_orgAn administrator hold applies to the organization.
admin_hold_connectionAn administrator hold applies to this connection.
meta_bannedMeta disabled or scheduled the account for disablement.
meta_offboardedMeta reports that the account is offboarded.
meta_messaging_restrictedMeta restricted messaging for the connection.
meta_token_invalidMeta authorization is invalid or expired.
meta_override_inactiveRequired webhook routing is inactive.
partner_removedMeta reports that the Dualhook connection was removed.
offboardingConnection removal is in progress.
connection_unavailableNo more specific public reason is safely available.

Do not rotate a key in response to this error. Resolve the connection hold in Dualhook.

Other authorization responses:

  • 401 — missing, invalid, revoked, expired, or quarantined credential.
  • 403 asset_scope_conflict — the stored phone/WABA assets are also assigned to a conflicting active connection, so routing fails closed.
  • 403 with an object-scope message — a media or template object is owned outside the selected connection.

Request Controls

The runtime enforces exact v25.0 routes, strict content types, explicit request schemas, field/query allowlists, bounded JSON, MIME-specific media limits, asset ownership, and manual redirects.

Raw Graph pagination URLs are never accepted or returned. Use the safe after or before cursor documented in the endpoint reference; Dualhook reconstructs the Graph request internally.

Every response includes X-Dualhook-Request-Id. Keep it in your content-free request log for support correlation.

Rate Limits

BucketDefaultScope
Messages80 operations/secondconnection
Media upload/delete20 operations/minuteconnection
Media metadata/download120 operations/minuteconnection
Phone/WABA health and status6 operations/minute per connection; 30/minute per WABAconnection and WABA
Management reads60 weighted units/minuteconnection and WABA
Management mutations10 weighted units/minuteconnection and WABA
Failed authentication20 rejected attempts/minutecredential

Phone and WABA reads share a six-per-minute connection allowance and a 30-per-minute WABA allowance. Successful responses are cached per connection for 60 seconds; X-Dualhook-Cache reports hit or miss, and cache hits include an Age header in seconds. Other single-object management reads cost 1 unit; template and QR lists cost 2. Profile, settings, and QR writes cost 2; a template header sample upload costs 4; template create/update costs 6; template deletion costs 10. One sample upload followed by one create therefore fits the default ten-unit mutation window.

After 30 failed Meta message requests within a rolling five-minute window, sending pauses for five minutes. Successful sends do not erase failures already accumulated in that window. A throttled response uses HTTP 429 and includes Retry-After.

Authentication is guarded separately before the database lookup. Once the database identifies a revoked or expired credential, that refusal is cached for up to 24 hours and continues returning 401. Other rejected credentials are throttled after 20 failed attempts within a minute: further requests receive HTTP 429 with Retry-After for five minutes. The throttle is keyed on the credential, not on your connection or IP address, so one dead key cannot slow down your other keys, and a key that authenticates successfully never accumulates against it. If you see this, stop retrying and check that the key is still active on the connection — a key revoked by connection cleanup or by a rotation cannot be revived, and a new one must be created on the connection's Overview page.

Privacy Model

Inbound customer-path webhooks remain direct from Meta to your endpoint. Outbound payloads and requested media transit Dualhook's Cloudflare Worker without body persistence or caching. Only successful, allowlisted phone/WABA metadata reads receive a connection-isolated 60-second response cache; no message, media, template, profile, or error response enters it.

Operational logs exclude request bodies, recipients, message content, media bytes, credentials, proofs, authorization headers, SIP credentials, encryption material, and raw Meta download or pagination URLs. Daily metrics contain only connection/organization scope, operation, status, latency, byte counts, safe Meta error metadata, and — on failed requests only — a truncated SHA-256 target fingerprint plus a failure reason and pipeline stage drawn from a fixed Dualhook vocabulary. The failure reason records why the runtime itself refused a request, never customer input, object IDs, or Meta message text; successful requests store no fingerprint, reason, or stage.

Related

Browse more docsStart Free Trial