Skip to main content
When your contract needs to send private data (e.g., PII — name, date of birth, email, etc.) to a third-party API, it does not read the values and inline them. Instead it uses the http-with-placeholders host interface: you put {{profile.<field>}} markers in the request, and the host resolves them from the calling user’s profile inside the enclave, just before the request goes out. The plaintext never enters your WASM.
  Agent  →  z:<tid>:contract              →  host (http-with-placeholders)  →  Duffel
   book-offer      templates {{profile.*}}        resolves the markers from        POST /orders
                   into the order body            the calling user's profile,      (real PII)
                                                  then sends the rendered request
   { id, pnr } ◀───────────────────────────────────────────────────────────────  { id, pnr }
Your contract in Rust:
use crate::bindings::t3n::host::http_with_placeholders as hwp;

// The {{profile.<path>}} markers are resolved host-side from the calling
// user's profile — this contract never sees the plaintext values.
let body = serde_json::json!({
    "data": {
        "type": "instant",
        "selected_offers": [req.offer_id],
        "passengers": [{
            "id": "passenger_0",
            "given_name":  "{{profile.first_name}}",
            "family_name": "{{profile.last_name}}",
            "born_on":     "{{profile.date_of_birth}}",
            "email":       "{{profile.verified_contacts.email.value}}",
        }]
    }
});

let resp = hwp::call(&hwp::Request {
    method:  "POST".to_string(),
    url:     "https://api.duffel.com/air/orders".to_string(),
    headers: vec![
        ("Authorization".to_string(), format!("Bearer {api_key}")),
        ("Duffel-Version".to_string(), "v2".to_string()),
        ("Content-Type".to_string(), "application/json".to_string()),
    ],
    body: Some(serde_json::to_vec(&body)?),
})?;
// resp.body — Duffel's response (booking id + PNR). The passport/name/DOB
// were substituted by the host; your WASM never held them.
Key points:
  • Synchronous. Like plain http, you get the upstream response back in the same invocation — there’s no deferred queue.
  • Profile access is gated by the user’s delegation. The markers resolve only when the calling agent is authorized to act for that user (see Invoke your contract). A marker your contract isn’t permitted to resolve fails with placeholder not permitted: <marker>.
  • Egress is the same rule as http. The target host must be on the user’s allowed-hosts grant, or the call is denied (host/http.egress_denied).
  • Markers reference the user profile schema — e.g. {{profile.first_name}}, {{profile.date_of_birth}}, {{profile.verified_contacts.email.value}}. Fields the schema doesn’t carry yet (passport, title) are supplied by your contract directly.