The default CCXML wrapper that quietly powers many VoiceXML applications on Voxeo’s Prophecy platform
is certainly sufficient for many simple applications. So why should I bother diving into yet another markup language to do re-invent the wheel? Because your application isn’t that simple and you’re a control freak. Or maybe I am. Who knows.
Typically, we can handle post-call cleanup in VoiceXML easily enough by using the <submit> or <data> elements to our server-side cleanup scripts.
<catch event="connection.disconnect">
<submit next="cleanup.php" method="POST"/>
</catch>
While this is a valid way to handle this, VoiceXML requires that we either transfer control to another document(<submit>) or respond with valid XML(<data>). Instead, we can leverage the disconnect event and any other portion of the application that we’d like the call to end to send this data back to our CCXML handler and let it do all the work asynchronously. The bonus here is we can have unfettered control of our voice applications. Once we have this in place, it’s simple work to leverage CCXML to control call duration, pre/post-processing and more.
When using a custom CCXML wrapper to handle post-call cleanup, our VoiceXML disconnect handler will look something like this:
<catch event="connection.disconnect">
<exit namelist="my_var1 my_var2 my_var3"/>
</catch>
Here we’re using the namelist attribute of <exit> to send back our values back to CCXML. We can now handle this inside our CCXML wrapper with a dialog.exit transition:
<transition event="dialog.exit">
<log expr="'**** DIALOG COMPLETE - SENDING POST CALL CLEANUP'"/>
<assign name="dialog_active" expr="false"/>
<log expr="'my_var1 = ' + event$.values.my_var1"/>
<log expr="'my_var2 = ' + event$.values.my_var2"/>
<log expr="'my_var3 = ' + event$.values.my_var3"/>
<assign name="my_var" expr="event$.values.my_var1"/>
<!-- sends a POST to our cleanup script -->
<disconnect connectionid="conn_id"/>
<send name="'user.call.cleanup'" target="'cleanup.php'" targettype="'basichttp'" namelist="my_var1"/>
</transition>
The values of our VXML variables are stored in the object event$.values. We can then grab them from the event$.values object by referencing them as event$.values.variablename – in this case, event$.values.my_var1. Since our VoiceXML dialog is complete, we’re ready to end the call. We issue a disconnect to the caller’s connectionid and send our post call cleanup. CCXML’s <disconnect>, unlike VoiceXML’s, physically disconnects the call leg. So now the call leg is ended, we’re free to handle our post-processing without paying to keep that call leg up. Brilliant!
Now that we’ve shot off our post-processing request, we can handle the send.successful event and close the session out, right? Well, we could do that, but how do we know the request was received successfully? Note, this next portion is a Voxeo specific extension and is not part of the W3C spec and requires the Voxeo namespace - <ccxml version=”1.0″ xmlns:voxeo=”http://community.voxeo.com/xmlns/ccxml”>.
Instead of assuming everything went swimmingly with our post-processing, we can be sure by having our server-side send back an event to the CCXML browser if we format the body of the response like this:
user.cleanup.successful
my_var1=foo
my_var2=bar
Note that we can inject not only an event here, but name/value pairs, though they are entirely optional. Now that my server side has responded, with an event, I’ll need to handle this in my CCXML:
<transition event="user.cleanup.successful">
<log expr="'**** POST CALL CLEANUP COMPLETED SUCCESSFULLY'"/>
<log expr="'**** EXITING SESSION'"/>
<exit/>
</transition>
Last, but very definitely not least, we will want to ensure our session does not stay alive after the call leg has disconnected. Since we are doing a little post-processing, we don’t want to end the session immediately, as we’ll want to give that time to process. So, we’ll simply shoot off a delayed user event to kill the session 60 seconds after a disconnect and prevent the zombie apocalypse.
<transition event="connection.disconnected">
<!-- send to unconditionally end a runaway session -->
<send name="'user.kill.unconditional'" target="session.id" delay="'60s'"/>
</transition>
<transition event="user.kill.unconditional">
<log expr="'**** UNCONDITIONAL KILL - EXITING SESSION'"/>
<exit/>
</transition>
That’s it. Find the complete CCXML below.
<?xml version="1.0"?>
<ccxml version="1.0" xmlns:voxeo="http://community.voxeo.com/xmlns/ccxml">
<meta name="author" content="Dustin Hayre"/>
<meta name="maintainer" content="YOUR_EMAIL@HERE.COM"/>
<!-- how long to wait before assuming a session is a runaway and tearing it down -->
<var name="conn_id"/>
<var name="dialog_id"/>
<var name="my_var"/>
<eventprocessor>
<transition event="connection.alerting">
<assign name="conn_id" expr="event$.connectionid"/>
<accept connectionid="conn_id"/>
</transition>
<transition event="connection.connected">
<log expr="'**** STARTING DIALOG TO CONNECTION ID ' + conn_id"/>
<!-- edit SRC attribute to point to VXML dialog -->
<dialogstart src="'dialog.vxml'" connectionid="conn_id" dialogid="dialog_id"/>
</transition>
<transition event="dialog.exit">
<log expr="'**** DIALOG COMPLETE - SENDING POST CALL CLEANUP'"/>
<log expr="'event$.values.my_var1 = ' + event$.values.my_var1"/>
<assign name="my_var" expr="event$.values.my_var1"/>
<!-- sends a POST to our cleanup script -->
<send name="'user.call.cleanup'" target="'cleanup.php'" targettype="'basichttp'" namelist="foo"/>
</transition>
<transition event="user.cleanup.successful">
<log expr="'**** POST CALL CLEANUP COMPLETED SUCCESSFULLY'"/>
<log expr="'**** EXITING SESSION'"/>
<exit/>
</transition>
<transition event="error.*">
<log expr="'**** ERROR - REASON: ' + event$.reason"/>
<exit/>
</transition>
<transition event="connection.disconnected">
<!-- send to unconditionally end a runaway session -->
<send name="'user.kill.unconditional'" target="session.id" delay="'60s'"/>
</transition>
<transition event="user.kill.unconditional">
<log expr="'**** UNCONDITIONAL KILL - EXITING SESSION'"/>
<exit/>
</transition>
<transition event="connection.failed">
<log expr="'**** CONNECTION FAILED - REASON: ' + event$.reason"/>
<exit/>
</transition>
</eventprocessor>
</ccxml>
Feel free to comment with any questions or suggestions.
-Dustin