Savon

Heavy metal SOAP client

GitHub RubyGems

Global Options

Global options are passed to the client’s constructor and are specific to a service. Although they are called “global options”, they really are local to a client instance.

Options marked with a deprecated under Faraday badge belong to the HTTPI transport layer. They keep working with the default transport: :httpi, but Savon rejects them when you opt into transport: :faraday. Faraday exposes its own setup API for proxies, timeouts, TLS, auth, redirects, and adapters. Configure those through client.faraday instead. See the transport option below for examples. If you mix them, Savon raises a Savon::InitializationError that points you at the matching Faraday call.

Service setup

wsdl

Savon accepts either a local or remote WSDL document which it uses to extract information like the SOAP endpoint and target namespace of the service. Alternatively, you can set the WSDL as a String.

Savon.client(wsdl: "https://example.com?wsdl")
Savon.client(wsdl: "/Users/me/project/service.wsdl")
Savon.client(wsdl: File.read("/Users/me/project/service.wsdl"))

For learning how to read a WSDL document, read the Beginner’s Guide by Thomas Bayer. It’s a good idea to know what you’re working with and this might really help you debug certain problems.

endpoint

The URL at which your service accepts SOAP requests. Required when your service doesn’t offer a WSDL. Can also be used to overwrite the endpoint defined in a WSDL document.

Savon.client(endpoint: "https://example.com", namespace: "http://v1.example.com")

In a WSDL, the SOAP endpoint is usually defined at the bottom as the location attribute of a soap:address node.

  <wsdl:service name="AuthenticationWebServiceImplService">
    <wsdl:port binding="tns:AuthenticationWebServiceImplServiceSoapBinding" name="AuthenticationWebServiceImplPort">
      <soap:address location="https://example.com/validation/1.0/AuthenticationService" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

namespace

The target namespace of the service, used to namespace the SOAP message. Required when your service doesn’t offer a WSDL. Can also be used to overwrite the namespace defined in a WSDL document.

Savon.client(endpoint: "https://example.com", namespace: "http://v1.example.com")

In a WSDL, the target namespace is defined on the wsdl:definitions (root) node, along with the service’s name and namespace declarations.

<wsdl:definitions
  name="AuthenticationWebServiceImplService"
  targetNamespace="http://v1_0.ws.auth.order.example.com/"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

host

Overrides the host (and port) of the endpoint that Savon extracts from the WSDL. The path stays intact, so this is useful for pointing a WSDL-defined service at a different environment (staging, a local mock, a regional host) without rewriting the WSDL or setting a full endpoint. Ignored when endpoint is set.

Savon.client(wsdl: "https://example.com?wsdl", host: "https://staging.example.com")

raise_errors

By default, Savon raises SOAP fault and HTTP errors. You can disable both errors and query the response instead.

Savon.client(raise_errors: false)

HTTP

proxy

deprecated under Faraday

You can specify a proxy server to use. This will be used for retrieving remote WSDL documents and actual SOAP requests.

Savon.client(proxy: "https://example.org")

headers

Additional HTTP headers for the request.

Savon.client(headers: { "Authentication" => "secret" })

open_timeout

deprecated under Faraday

How long Savon waits (in seconds) to establish a connection. Used for both retrieving remote WSDL documents and sending SOAP requests.

Savon.client(open_timeout: 5)

read_timeout

deprecated under Faraday

How long Savon waits (in seconds) for the server to send response data after the request has been sent. Used for both retrieving remote WSDL documents and sending SOAP requests.

Savon.client(read_timeout: 5)

write_timeout

deprecated under Faraday

How long Savon waits (in seconds) when sending the request body. Useful when uploading large payloads or attachments.

Savon.client(write_timeout: 30)

follow_redirects

deprecated under Faraday

Whether requests should follow HTTP redirects. Defaults to false. Requires an HTTPI adapter that supports redirect following; with transport: :faraday, configure faraday-follow-redirects on client.faraday instead.

Savon.client(follow_redirects: true)

adapter

deprecated under Faraday

Selects the HTTPI adapter used by the client.

Savon.client(wsdl: "https://example.com?wsdl", adapter: :httpclient)

This option is HTTPI-specific. With transport: :faraday, configure the adapter through client.faraday instead.

transport

Defaults to :httpi using HTTPI. Set to :faraday to use a Faraday connection instead.

Savon.client(wsdl: "...", transport: :faraday)

When using the Faraday transport, access client.faraday to configure the connection before making any calls. It returns a Faraday::Connection that you configure directly:

client = Savon.client(wsdl: "...", transport: :faraday)

conn = client.faraday
conn.headers["Authorization"] = "Bearer token"
conn.options.timeout = 10
conn.ssl.verify = false

The Faraday transport unlocks features that HTTPI does not support: redirect following for WSDL fetches (via faraday-follow-redirects) and digest authentication (via faraday-digestauth).

Note: httpi-specific options like proxy, open_timeout, read_timeout, SSL options, and adapter cannot be used alongside transport: :faraday. Savon will raise a helpful error if you mix them. Configure those through client.faraday instead.

SSL

These will be used for retrieving remote WSDL documents and actual SOAP requests.

ssl_verify_mode

deprecated under Faraday

You can disable SSL verification if you know what you’re doing.

Savon.client(ssl_verify_mode: :none)

ssl_version

deprecated under Faraday

Pins the TLS protocol version to use. Prefer ssl_min_version and ssl_max_version when you can allow the client and server to negotiate within a TLS range.

Savon.client(ssl_version: :TLSv1_2)

ssl_min_version

deprecated under Faraday

Sets the lowest TLS version allowed during the handshake. Useful when you want to refuse anything below a known-safe floor while still allowing the server and client to negotiate a newer version.

Savon.client(ssl_min_version: :TLS1_2)

ssl_max_version

deprecated under Faraday

Sets the highest TLS version allowed during the handshake. Pair with ssl_min_version to constrain negotiation to a version range instead of pinning a single version with ssl_version.

Savon.client(ssl_max_version: :TLS1_3)

ssl_ciphers

deprecated under Faraday

Restrict the cipher suites offered during the TLS handshake. The value is passed straight through to OpenSSL, so any cipher string accepted by your OpenSSL build works.

# Only high-strength suites, no anonymous auth, no MD5
Savon.client(ssl_ciphers: "HIGH:!aNULL:!MD5")

ssl_cert_file

deprecated under Faraday

Sets the client certificate file to use for TLS client authentication.

Savon.client(ssl_cert_file: "lib/client_cert.pem")

ssl_cert

deprecated under Faraday

Object form of ssl_cert_file. Pass a preloaded OpenSSL::X509::Certificate instead of a file path. Maps directly to HTTPI’s ssl.cert.

cert = OpenSSL::X509::Certificate.new(File.read("lib/client_cert.pem"))
Savon.client(ssl_cert: cert)

ssl_cert_key_file

deprecated under Faraday

Sets the SSL cert key file to use.

Savon.client(ssl_cert_key_file: "lib/client_key.pem")

ssl_cert_key

deprecated under Faraday

Object form of ssl_cert_key_file. Pass a preloaded OpenSSL::PKey instance instead of a file path. Pair with ssl_cert_key_password if the key was encrypted on disk.

key = OpenSSL::PKey.read(File.read("lib/client_key.pem"))
Savon.client(ssl_cert_key: key)

ssl_ca_cert_file

deprecated under Faraday

Sets the trusted CA certificate file to use for peer verification. Use ssl_ca_cert_path for a directory of trusted CA certificates.

Savon.client(ssl_ca_cert_file: "lib/ca_cert.pem")

ssl_ca_cert

deprecated under Faraday

Object form of ssl_ca_cert_file. Pass a preloaded OpenSSL::X509::Certificate to use as the trust anchor for peer verification. For multiple CA certificates, build a store with ssl_cert_store instead.

ca_cert = OpenSSL::X509::Certificate.new(File.read("lib/ca_cert.pem"))
Savon.client(ssl_ca_cert: ca_cert)

ssl_ca_cert_path

deprecated under Faraday

Path to a directory of trusted CA certificates. Use this when you have a directory of certs instead of a single bundle file.

Savon.client(ssl_ca_cert_path: "/etc/ssl/certs")

ssl_cert_store

deprecated under Faraday

An OpenSSL::X509::Store to use for certificate verification. Useful when you build a custom trust store at boot and want every Savon client to share it.

store = OpenSSL::X509::Store.new
store.set_default_paths
store.add_file("config/internal_ca.pem")

Savon.client(ssl_cert_store: store)

ssl_cert_key_password

deprecated under Faraday

Sets the cert key password to decrypt an encrypted private key.

Savon.client(ssl_cert_key_password: "secret")

Request

convert_request_keys_to

Savon tells Gyoku to convert SOAP message Hash key Symbols to lowerCamelcase tags. You can change this to CamelCase, UPCASE or completely disable any conversion.

Value :user_name becomes
:lower_camelcase (default) <userName>
:camelcase <UserName>
:upcase <USER_NAME>
:none <user_name>
client = Savon.client do
  convert_request_keys_to :camelcase
end

client.call(:find_user) do
  message(user_name: "luke")
end

This example converts all keys in the request Hash to CamelCase tags.

<env:Envelope
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:wsdl="http://v1.example.com">
  <env:Body>
    <wsdl:FindUser>
      <UserName>luke</UserName>
    </wsdl:FindUser>
  </env:Body>
</env:Envelope>

soap_header

If you need to add custom XML to the SOAP header, you can use this option. This might be useful for setting a global authentication token or any other kind of metadata.

Savon.client(soap_header: { "Token" => "secret" })

This is the header created for the options:

<env:Envelope
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:v1="http://v1.example.com/">
  <env:Header>
    <Token>secret</Token>
  </env:Header>
</env:Envelope>

element_form_default

Savon should extract whether to qualify elements from the WSDL. If there is no WSDL, Savon defaults to :unqualified.

If you specified a WSDL but still need to use this option, please open an issue and make sure to add your WSDL for debugging. Savon currently does not support WSDL imports, so in case your service imports its type definitions from another file, the element_form_default value might be wrong.

Savon.client(element_form_default: :qualified)

env_namespace

Savon defaults to use :env as the namespace identifier for the SOAP envelope. If that doesn’t work for you, I would like to know why. So please open an issue and make sure to add your WSDL for debugging.

Savon.client(env_namespace: :soapenv)

This is how the request’s envelope looks like after changing the namespace identifier:

<soapenv:Envelope
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

namespace_identifier

Should be extracted from the WSDL. If it doesn’t have a WSDL, Savon falls back to :wsdl. No idea why anyone would need to use this option.

Savon.client(namespace_identifier: :v1)

Notice the v1:authenticate message tag in the generated request:

<env:Envelope
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:v1="http://v1.example.com/">
  <env:Body>
    <v1:authenticate></v1:authenticate>
  </env:Body>
</env:Envelope>

namespaces

You can add additional namespaces to the SOAP envelope tag.

namespaces = {
  "xmlns:v2" => "http://v2.example.com",
}

Savon.client(namespaces: namespaces)

This does what you would expect it to do. If you need to use this option, please open an issue and provide your WSDL for debugging.

<env:Envelope
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:v1="http://v1.example.com/"
    xmlns:v2="http://v2.example.com/">
  <env:Body>
    <v1:authenticate></v1:authenticate>
  </env:Body>
</env:Envelope>

encoding

Savon defaults to UTF-8.

Savon.client(encoding: "UTF-16")

Changing the default affects both the Content-Type header:

{ "Content-Type" => "text/xml;charset=UTF-16" }

and the XML instruction:

<?xml version="1.0" encoding="UTF-16"?>

soap_version

Defaults to SOAP 1.1. Set to 2 to use SOAP 1.2, which changes the envelope namespace and the request Content-Type accordingly.

Savon.client(soap_version: 2)

use_wsa_headers

Adds WS-Addressing headers to every request. wsa:Action reuses the request’s SOAPAction, wsa:To is the endpoint, and wsa:MessageID is a fresh urn:uuid per request. Defaults to false. Enable it when the service requires WS-Addressing.

Savon.client(use_wsa_headers: true)

The resulting <env:Header> looks like this:

<env:Header>
  <wsa:Action>urn:Authenticate</wsa:Action>
  <wsa:To>https://example.com/service</wsa:To>
  <wsa:MessageID>urn:uuid:7e3f...</wsa:MessageID>
</env:Header>

no_message_tag

Skips the SOAP message tag that Savon usually wraps around the body. With no_message_tag: true, the message (or raw xml) is inserted directly inside <env:Body> instead of being wrapped in the operation name tag. Useful for services that expect a custom root element you build yourself. Defaults to false.

Savon.client(no_message_tag: true)

unwrap

Tells Gyoku to unwrap an Array of Hashes when building the request. Without it, Gyoku wraps each Hash in a parent tag matching the key. With unwrap: true, the parent tag is repeated for each entry instead.

Savon.client(unwrap: true)

client.call(:create_users, message: {
  users: [{ name: "luke" }, { name: "lea" }]
})
<users>
  <name>luke</name>
</users>
<users>
  <name>lea</name>
</users>

Defaults to false.

Authentication

HTTP authentication will be used for retrieving remote WSDL documents and actual SOAP requests.

basic_auth

deprecated under Faraday

Savon supports HTTP basic authentication.

Savon.client(basic_auth: ["luke", "secret"])

digest_auth

deprecated under Faraday

And HTTP digest authentication. If you wish to use digest auth you must ensure that you have included the gem httpclient, or another one of the HTTPI adapters that supports HTTP digest authentication. Failing to do so will not produce errors, but if the HTTPI adapter ends up using net_http, digest authentication will not be performed.

Savon.client do
  digest_auth("lea", "top-secret")
end

wsse_auth

As well as WSSE basic/digest auth.

Savon.client(wsse_auth: ["lea", "top-secret"])

Savon.client do
  wsse_auth("lea", "top-secret", :digest)
end

wsse_timestamp

Adds a WS-Security timestamp to every request. Many enterprise services require this even when their documentation doesn’t mention it. If you’re getting authentication failures or wsse:InvalidSecurity faults despite correct credentials, adding this option is often the fix.

Savon.client(wsse_timestamp: true)

Combine with wsse_auth when the service requires both credentials and a timestamp:

Savon.client(
  wsse_auth: ["username", "password", :digest],
  wsse_timestamp: true
)

wsse_signature

XML Signature support via Akami. Sign the SOAP envelope with an X.509 certificate when the service requires it.

wsse_signature = Akami::WSSE::Signature.new(
  Akami::WSSE::Certs.new(
    cert_file: "client_cert.pem",
    private_key_file: "client_key.pem"
  )
)

Savon.client(wsdl: "https://example.com?wsdl", wsse_signature:)

This is independent of wsse_auth. A service may require either, both, or neither.

ntlm

deprecated under Faraday

HTTPI v2.1.0 supports NTLM authentication through its :net_http adapter. The optional third argument allows you to specify a domain. If the domain is omitted, it is assumed you want to authenticate with the local server.

Savon.client(ntlm: ["username", "password"])
Savon.client(ntlm: ["username", "password", "domain"])

Response

strip_namespaces

Savon configures Nori to strip any namespace identifiers from the response. If that causes problems for you, you can disable this behavior.

Savon.client(strip_namespaces: false)

Here’s how the response Hash would look like if namespaces were not stripped from the response:

response.hash["soap:envelope"]["soap:body"]["ns2:authenticate_response"]

convert_response_tags_to

Savon tells Nori to convert any XML tag from the response to a snakecase Symbol. This is why accessing the response as a Hash looks natural:

response.body[:user_response][:id]

You can specify your own Proc or any object that responds to #call. It is called for every XML tag and simply has to return the converted tag.

upcase = lambda { |key| key.snakecase.upcase }
Savon.client(convert_response_tags_to: upcase)

You can have it your very own way.

response.body["USER_RESPONSE"]["ID"]

delete_namespace_attributes

Tells Nori to drop xmlns:* attributes from the response. Defaults to false. Enable it when those attributes survive strip_namespaces and clutter the Hash you actually want to work with.

Savon.client(delete_namespace_attributes: true)

convert_attributes_to

Tells Nori how to convert XML attributes on response tags into Hash keys. Accepts a lambda or block that receives (key, value) and returns a [key, value] pair. Defaults to passing them through unchanged.

Savon.client(convert_attributes_to: ->(key, value) { [key.snakecase.to_sym, value] })

empty_tag_value

The value Nori assigns to empty XML tags in the response. Defaults to nil, matching Nori’s default. Set it to "" to map empty tags to an empty String instead.

Savon.client(empty_tag_value: "")

convert_dashes_to_underscores

Tells Nori whether dashes in response tag names should be converted to underscores before they become Hash keys. Defaults to true, so <user-name> parses to :user_name. Disable it to preserve dashes in the keys.

Savon.client(convert_dashes_to_underscores: false)

scrub_xml

Tells Nori whether to scrub invalid byte sequences from the response body before parsing it. Defaults to true, which lets responses containing invalid characters still be parsed. Set to false to surface those bytes as parser errors instead.

Savon.client(scrub_xml: false)

multipart

deprecated

No-op since v2.13.0. Savon detects multipart (MTOM) responses from the Content-Type header and parses them regardless of this option, so it never enabled or disabled anything. Safe to remove. When the response is multipart, response.attachments exposes the parts attached to the response.

Logging

logger

Savon logs to $stdout using Ruby’s default Logger. Can be changed to any compatible logger.

Savon.client(logger: Rails.logger)

log_level

Can be used to limit the amount of log messages by increasing the severity. Translates the Logger’s integer values to Symbols for developer happiness.

Savon.client(log_level: :info)  # or one of [:debug, :warn, :error, :fatal]

log

Specifies whether Savon should log requests or not. Silences HTTPI as well.

Savon.client(log: false)

filters

Sensitive information should probably be removed from logs. If you don’t have a central way of filtering your logs, you can tell Savon about the message parameters to filter for you.

Savon.client(filters: [:password])

This filters the password in both the request and response.

<env:Envelope
    xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'
    xmlns:tns='http://v1_0.ws.auth.order.example.com/'>
  <env:Body>
    <tns:authenticate>
      <username>luke</username>
      <password>***FILTERED***</password>
    </tns:authenticate>
  </env:Body>
</env:Envelope>

pretty_print_xml

Pretty print the request and response XML in your logs for debugging purposes.

Savon.client(pretty_print_xml: true)

log_headers

Whether HTTP request and response headers are logged alongside the bodies. Defaults to true. Turn it off when headers carry tokens or cookies you do not want in your log output and you cannot rely on filters (which only redacts XML elements in the body).

Savon.client(log: true, log_headers: false)