Savon

Heavy metal SOAP client

GitHub RubyGems

Local Options

Local options are passed to the client’s #call method and are specific to a single request.

HTTP

soap_action

You might need to set this if you don’t have a WSDL. Otherwise, Savon should set the proper SOAPAction HTTP header for you. If it doesn’t, please open an issue and add the WSDL of your service.

client.call(:authenticate, soap_action: "urn:Authenticate")

cookies

Savon 2.0 tried to automatically handle cookies by storing the cookies from the last response and using them for the next request. This is wrong and it caused problems. Savon 2.1 does not set the “Cookie” header for you, but it makes it easy for you to handle cookies yourself.

response = client.call(:authenticate, message: credentials)
auth_cookies = response.http.cookies

client.call(:find_user, message: { id: 3 }, cookies: auth_cookies)

This option accepts an Array of HTTPI::Cookie objects or any object that responds to cookies (like for example, an HTTPI::Response).

headers

Per-request HTTP headers. Merged with the global headers option, so you can keep a base set on the client and add or override just what changes for this call.

client.call(:find_user,
  message: { id: 42 },
  headers: { "X-Request-Id" => SecureRandom.uuid }
)

Request

message

You probably want to add some arguments to your request. For simple XML which can easily be represented as a Hash, you can pass the SOAP message as a Hash. Savon uses Gyoku to translate the Hash into XML.

client.call(:authenticate, message: { username: 'luke', password: 'secret' })

For more complex XML structures, you can pass any other object that is not a Hash and responds to #to_s if you want to use a more specific tool to build your request.

class ServiceRequest

  def to_s
    builder = Builder::XmlMarkup.new
    builder.instruct!(:xml, encoding: "UTF-8")

    builder.person { |b|
      b.username("luke")
      b.password("secret")
    }

    builder
  end

end

client.call(:authenticate, message: ServiceRequest.new)

message_tag

You can change the name of the SOAP message tag. If you need to use this option, please open an issue let me know why.

client.call(:authenticate, message_tag: :authenticationRequest)

This should be set by Savon if it has a WSDL. If it doesn’t, it generates a message tag from the SOAP operation name. Here’s how the option changes the request.

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

attributes

The attributes option accepts a Hash of XML attributes for the SOAP message tag.

client.call(:authenticate, :attributes => { "ID" => "ABC321" })

Here’s what the request will look like.

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

If you need to use this option, please open an issue and provide you WSDL for debugging. This should be handled automatically, but we need real world examples to do so.

soap_header

Since v2.3.0 you can specify the SOAP header per request. When both the global and local option is used, Savon will merge the global with the local Hash and the local keys win.

client.call(:authenticate, :soap_header => { "OpToken" => "secret" })

xml

If you need to, you can even shortcut Savon’s Builder and send your very own XML.

client.call(:authenticate, xml: "<envelope><body></body></envelope>")

attachments

Send SOAP-with-Attachments parts alongside the SOAP envelope. When attachments is present, Savon sends the request as multipart/related. The SOAP envelope is the root MIME part and each attachment is added as a separate part.

Savon does not add attachment references to the SOAP body automatically. Build the SOAP message so it references the attachment Content-ID expected by your service, usually with a cid: URL.

Pass an Array of hashes with :filename and :content. The filename becomes the attachment Content-ID and Content-Location:

client.call(:upload,
  message: {
    document: "",
    :attributes! => { document: { href: "cid:report.xml" } }
  },
  attachments: [
    { filename: "report.xml", content: "<xml>...</xml>" }
  ]
)

The multipart body will contain a SOAP root part and an attachment part similar to this, with boundaries and some Mail-generated headers omitted for clarity:

Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: 7bit
Content-ID: <soap-request-body@soap>

<env:Envelope ...>
  <env:Body>
    <tns:upload>
      <document href="cid:report.xml"></document>
    </tns:upload>
  </env:Body>
</env:Envelope>

--boundary
Content-Type: application/xml; filename=report.xml
Content-Transfer-Encoding: base64
Content-Location: report.xml
Content-ID: <report.xml>

PHhtbD4uLi48L3htbD4=

Pass a Hash of "content-id" => path to read attachments from disk. The Hash key becomes the attachment Content-ID, independent of the file basename:

client.call(:upload,
  message: {
    document: "",
    :attributes! => { document: { href: "cid:invoice" } }
  },
  attachments: {
    "invoice" => "/tmp/invoice-2026-05.pdf"
  }
)

Pass an Array of paths to use each file’s basename as the attachment Content-ID:

client.call(:upload,
  message: { user_id: 42 },
  attachments: [
    "/tmp/report.xml",
    "/tmp/scan.pdf"
  ]
)

Those parts can be referenced from the SOAP body as cid:report.xml and cid:scan.pdf.

This feature sends generic SOAP-with-Attachments MIME parts. Savon does not create MTOM/XOP xop:Include elements, optimize base64 content, or switch the request to MTOM application/xop+xml framing. If your service requires a specific element name, namespace, or attribute for attachment references, build that XML in the message or xml option and make sure its cid: value matches the attachment Content-ID.

Authentication

The three WSSE options accepted by the client constructor can also be passed per request. The local value overrides the global for that single call. Use this when one operation needs different credentials, a different signing key, or needs to disable a header that’s normally set globally.

wsse_auth

Per-request WSSE basic or digest credentials. Overrides the global wsse_auth for this call only.

client.call(:authenticate, wsse_auth: ["lea", "top-secret", :digest])

Pass false to suppress a globally configured wsse_auth for one call:

client.call(:public_lookup, wsse_auth: false)

wsse_timestamp

Per-request WS-Security timestamp. Overrides the global wsse_timestamp for this call.

client.call(:authenticate, wsse_timestamp: true)

wsse_signature

Per-request XML Signature via Akami. Overrides the global wsse_signature for this call.

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

client.call(:sign_document, wsse_signature: signature)

Response

advanced_typecasting

Savon by default tells Nori to use its “advanced typecasting” to convert XML values like "true" to TrueClass, dates to date objects, etc.

client.call(:authenticate, advanced_typecasting: false)

response_parser

Savon defaults to Nori’s Nokogiri parser. Nori ships with a REXML parser as an alternative. If you need to switch to REXML, please open an issue and describe the problem you have with the Nokogiri parser.

client.call(:authenticate, response_parser: :rexml)

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 for the call. Safe to remove. When the response is multipart, response.attachments exposes the parts attached to the response.