diff --git a/gracedb/api.py b/gracedb/api.py
index 30d71fc601aa1952f98e5546b19b2c45ed5b63f0..b44a50c2189c7b8db2339a731e0436de12fdadd0 100644
--- a/gracedb/api.py
+++ b/gracedb/api.py
@@ -15,6 +15,7 @@ from gracedb.views import create_label
 from translator import handle_uploaded_data
 
 from alert import issueAlertForUpdate
+from buildVOEvent import buildVOEvent
 
 import os
 import urllib
@@ -282,6 +283,34 @@ class TSVRenderer(BaseRenderer):
        
         return outTable
 
+# XXX this doesn't work because you don't have the request here. You could
+# try stuffing it into the renderer context, but that's really ugly. 
+#class VOEventRenderer(BaseRenderer):
+#    media_type = 'application/xml'
+#    format = 'xml'
+#
+#    def render(self, data, media_type=None, renderer_context=None):
+#        logger = logging.getLogger(__name__)
+#        logger.debug("inside voevent renderer")
+#        if 'error' in data.keys():
+#            return data['error']
+#
+#        outDoc = ''
+#        for e in data['events']:
+#            graceid = e['graceid']
+#
+#            try:
+#                # XXX If any part of this fails, the VOEvent will be empty.
+#                event = Event.getByGraceid(graceid)
+#                if not event.far or not event.gpstime:
+#                    raise Exception
+#                voevent = buildVOEvent(event, request)
+#            except:
+#                voevent = ''
+#            outDoc += voevent + '\n'
+#       
+#        return outDoc
+
 #==================================================================
 # Events
 
@@ -669,6 +698,36 @@ class EventDetail(APIView):
                     status=status.HTTP_400_BAD_REQUEST)
         return Response(status=status.HTTP_202_ACCEPTED)
 
+# FIXME or something.
+# This should really be a renderer and not a view. But the problem 
+# is that the renderer needs the request in order to build up URLs.
+# There must be a better way of doing this.
+class EventVODetail(APIView):
+    authentication_classes = (LigoAuthentication,)
+    #parser_classes = (LigoLwParser, RawdataParser)
+    parser_classes = (parsers.MultiPartParser,)
+    #serializer_class = EventSerializer
+    permission_classes = (IsAuthenticated,)
+    renderer_classes = (JSONRenderer, BrowsableAPIRenderer, )
+
+    def get(self, request, graceid):
+        try:
+            event = Event.getByGraceid(graceid)
+        except Event.DoesNotExist:
+            # XXX Real error message.
+            return Response("Event Not Found",
+                    status=status.HTTP_404_NOT_FOUND)
+
+        try:
+            voevent = buildVOEvent(event,request)
+        except Exception, e:
+            return Response("Problem building VOEvent: %s" % str(e),
+                    status=HTTP_500_INTERNAL_SERVER_ERROR)
+
+        response = Response(voevent)
+        response["Cache-Control"] = "no-cache"
+        return response
+
 #==================================================================
 # Neighbors
 
@@ -1188,6 +1247,8 @@ class GracedbRoot(APIView):
         # Is there better?
         detail = reverse("event-detail", args=["G1200"], request=request)
         detail = detail.replace("G1200", "{graceid}")
+        vo_detail = reverse("event-vo-detail", args=["G1200"], request=request)
+        vo_detail = vo_detail.replace("G1200", "{graceid}")
         log = reverse("eventlog-list", args=["G1200"], request=request)
         log = log.replace("G1200", "{graceid}")
 
@@ -1216,6 +1277,7 @@ class GracedbRoot(APIView):
 
         templates = {
                 "event-detail-template" : detail,
+                "event-vo-detail-template" : vo_detail,
                 "event-log-template" : log,
                 "event-label-template" : labels,
                 "files-template" : files,
diff --git a/gracedb/buildVOEvent.py b/gracedb/buildVOEvent.py
index f04e83d055e710ab46a9dacf4a86653d9e922dab..ac35bf6b04e549e0e634351c293618c61e107688 100755
--- a/gracedb/buildVOEvent.py
+++ b/gracedb/buildVOEvent.py
@@ -8,7 +8,7 @@ See the VOEvent specification for details
 http://www.ivoa.net/Documents/latest/VOEvent.html
 """
 
-from VOEventLib.VOEvent import VOEvent, Who, What, Author, Param
+from VOEventLib.VOEvent import VOEvent, Who, What, Author, Param, How, Why
 from VOEventLib.Vutil import makeWhereWhen, stringVOEvent
 
 # XXX ER2.utils.  utils is in project directory.  ugh.
@@ -29,11 +29,28 @@ def buildVOEvent(gevent, request=None, description=None, role=None):
     ############ Who ############################
     w = Who()
     a = Author()
-    a.add_contactName("LIGO Scientific Consortium")
-    a.add_contactEmail("postmaster@ligo.org")
+    a.add_contactName("LIGO Scientific Collaboration and Virgo Collaboration")
+    #a.add_contactEmail("postmaster@ligo.org")
     w.set_Author(a)
     v.set_Who(w)
 
+    ############ Why ############################
+    y = Why()
+    y.add_Description("Candidate gravitational wave event identified by low-latency analysis")
+    v.set_Why(y)
+
+    ############ How ############################
+
+    h = How()
+    instruments = gevent.instruments.split(',')
+    if 'H1' in instruments:
+        h.add_Description("H1: LIGO Hanford 4 km gravitational wave detector")
+    if 'L1' in instruments:
+        h.add_Description("L1: LIGO Livingston 4 km gravitational wave detector")
+    if 'V1' in instruments:
+        h.add_Description("V1: Virgo 3 km gravitational wave detector")
+    v.set_How(h)
+
     ############ What ############################
     w = What()
 
@@ -53,16 +70,30 @@ def buildVOEvent(gevent, request=None, description=None, role=None):
     # basically, a string that makes sense to humans about what units a value is. eg. "m/s"
 
     # params related to the event. None are in Groups.
-    p = Param(name="gracedbid", ucd="meta.id", value="%s"% objid)
-    p.set_Description(["Identifier assigned by gracedb"])
+    p = Param(name="GraceID", ucd="meta.id", value="%s"% objid)
+    p.set_Description(["Identifier in GraceDB"])
     w.add_Param(p)
 
-    p = Param(name="gpstime", ucd="time.epoch", dataType="int",  value=str(gevent.gpstime))
-    p.set_Description(["GPS time of the trigger"])
-    w.add_Param(p)
+    try:
+        p = Param(name="FAR", dataType="float", ucd="arith.rate;stat.falsealarm", unit="Hz", 
+                value=float(gevent.far))
+        p.set_Description(["False alarm rate for GW candidates with this strength or greater"])
+        w.add_Param(p)
+    except:
+        pass
 
-    p = Param(name="likelihood", ucd="stat.likelihood", dataType="float",  value=str(gevent.likelihood))
-    p.set_Description(["Likelihood"])
+    #p = Param(name="gpstime", ucd="time.epoch", dataType="int",  value=str(gevent.gpstime))
+    #p.set_Description(["GPS time of the trigger"])
+    #w.add_Param(p)
+
+    #p = Param(name="likelihood", ucd="stat.likelihood", dataType="float",  value=str(gevent.likelihood))
+    #p.set_Description(["Likelihood"])
+    #w.add_Param(p)
+
+    shib_event_page_url = reverse("view2", args=[gevent.graceid()])
+    shib_event_page_url = request.build_absolute_uri(shib_event_page_url)
+    p = Param(name="EventPage", ucd="meta.ref.url", value=shib_event_page_url)
+    p.set_Description(["Web page for evolving status of this candidate event"])
     w.add_Param(p)
 
     # For GCN / SkyAlert.
@@ -73,27 +104,21 @@ def buildVOEvent(gevent, request=None, description=None, role=None):
     # IFO list  dataType="string"   ucd=
     # URL to skymap   Reference/URL
 
-    p = Param(name="analysistype", dataType="string", value=str(gevent.get_analysisType_display()))
-    p.set_Description(["LIGO analysis which produced this result"])
+    p = Param(name="SearchType", dataType="string", value=str(gevent.get_analysisType_display()))
+    p.set_Description(["Low-latency search type"])
     w.add_Param(p)
 
-    try:
-        p = Param(name="far", dataType="float", ucd="arith.rate", unit="Hz", value=float(gevent.far))
-        p.set_Description(["False Alarm Rate"])
-        w.add_Param(p)
-    except:
-        pass
 
-    p = Param(name="ifolist", dataType="string", value=str(gevent.instruments))
-    p.set_Description(["Interferometers"])
-    w.add_Param(p)
+    #p = Param(name="ifolist", dataType="string", value=str(gevent.instruments))
+    #p.set_Description(["Interferometers"])
+    #w.add_Param(p)
 
     # Skymaps
     #  Four of them, per Roy Williams.  (FITS and PNG) x  (x509 auth and Shib auth)
 
-    # relative URLs
-    x509_fits_skymap_url = reverse("file", args=[gevent.graceid(), "general/bayestar/skymap.fits"])
-    x509_png_skymap_url = reverse("file", args=[gevent.graceid(), "general/bayestar/skymap.png"])
+    # relative URLs (huh? -- Branson)
+    #x509_fits_skymap_url = reverse("file", args=[gevent.graceid(), "skymap.fits"])
+    #x509_png_skymap_url = reverse("file", args=[gevent.graceid(), "skymap.png"])
 
     # XXX gracedb.ligo.org urls.  they are a little problematic.
     # they do not do mime-types correctly and they do not let go of the connection for some reason.
@@ -101,8 +126,15 @@ def buildVOEvent(gevent, request=None, description=None, role=None):
     #shib_png_skymap_url = reverse("file", args=[gevent.graceid(), "general/bayestar/skymap.png"])
 
     # Old sad bad ldad-jobs urls
-    shib_fits_skymap_url = gevent.weburl() + "/general/bayestar/skymap.fits"
-    shib_png_skymap_url  = gevent.weburl() + "/general/bayestar/skymap.png"
+    # Confused. These are protected by shib, but so are the others.  And these are ugly.
+    # shib_fits_skymap_url = gevent.weburl() + "/private/skymap.fits"
+    # shib_png_skymap_url  = gevent.weburl() + "/private/skymap.png"
+    shib_fits_skymap_url = reverse("file", args=[gevent.graceid(), "skymap.fits"])
+    shib_png_skymap_url = reverse("file", args=[gevent.graceid(), "skymap.png"])
+
+    # x509 urls. Hafta specify the api namespace.
+    x509_fits_skymap_url = reverse("x509:files", args=[gevent.graceid(), "skymap.fits"])
+    x509_png_skymap_url = reverse("x509:files", args=[gevent.graceid(), "skymap.png"])
 
     # Need request to build absolute URL
     # XXX should probably be an error if we can't give the full absolute url.
@@ -145,13 +177,13 @@ def buildVOEvent(gevent, request=None, description=None, role=None):
     v.set_What(w)
 
     ############ Wherewhen ############################
-    wwd = {'observatory':     'LIGO',
+    wwd = {'observatory':     'LIGO Virgo',
            'coord_system':    'UTC-FK5-GEO',
            # XXX time format
            'time':            str(gpsToUtc(gevent.gpstime).isoformat())[:-6],   #'1918-11-11T11:11:11',
-           'timeError':       1.0,
-           'longitude':       123.45,
-           'latitude':        67.89,
+           #'timeError':       1.0,
+           'longitude':       0.0,
+           'latitude':        0.0,
            'positionalError': 180.0,
     }
 
diff --git a/gracedb/urls_rest.py b/gracedb/urls_rest.py
index 828bfec477bde347f8b5592539e343a79e6f9209..89e6faea18a50cf79069f7447d3cdd4aa48c1f84 100644
--- a/gracedb/urls_rest.py
+++ b/gracedb/urls_rest.py
@@ -3,7 +3,7 @@ from django.conf.urls.defaults import patterns, url
 
 # rest_framework
 from gracedb.api import GracedbRoot
-from gracedb.api import EventList, EventDetail
+from gracedb.api import EventList, EventDetail, EventVODetail
 from gracedb.api import EventLogList, EventLogDetail
 from gracedb.api import TagList
 # from gracedb.api import TagDetail
@@ -20,6 +20,8 @@ urlpatterns = patterns('gracedb.api',
     # events/[{graceid}[/{version}]]
     url (r'events/$',
         EventList.as_view(), name='event-list'),
+    url (r'events/voevent/(?P<graceid>[GEHT]\d+)$',
+        EventVODetail.as_view(), name='event-vo-detail'),
     url (r'events/(?P<graceid>[GEHT]\d+)$',
         EventDetail.as_view(), name='event-detail'),
 
diff --git a/ligoauth/migrations/0006_add_gcn_sender_user_and_dn.py b/ligoauth/migrations/0006_add_gcn_sender_user_and_dn.py
new file mode 100644
index 0000000000000000000000000000000000000000..c9b3db36ce4cba328c1e186f0c4b456e2799bdd0
--- /dev/null
+++ b/ligoauth/migrations/0006_add_gcn_sender_user_and_dn.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+
+
+users = [
+        {
+            'username' : 'gcn_sender',
+            'first_name' : '',
+            'last_name' : 'GCN Sender',
+            'email' : 'branson@gravity.phys.uwm.edu',
+            'dns' : [
+                "/DC=org/DC=ligo/O=LIGO/OU=Services/CN=gcn_sender/archie.phys.uwm.edu",
+                "/DC=org/DC=ligo/O=LIGO/OU=Services/CN=gcn_sender/gracedb.phys.uwm.edu",
+            ]
+        },
+]
+
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        LocalUser = orm['ligoauth.LocalUser']
+        X509Cert = orm['ligoauth.X509Cert']
+
+        # Local Users
+        for entry in users:
+            user, created = LocalUser.objects.get_or_create(username=entry['username'])
+            if created:
+                user.first_name = entry['first_name']
+                user.last_name = entry['last_name']
+                user.email = entry['email']
+                user.is_active = True
+                user.is_staff = False
+                user.is_superuser = False
+                user.save()
+            current_dns = set([cert.subject for cert in user.x509cert_set.all()])
+            new_dns = set(entry['dns'])
+
+            missing_dns = new_dns - current_dns
+            redundant_dns = current_dns - new_dns
+
+            for dn in missing_dns:
+                cert, created = X509Cert.objects.get_or_create(subject=dn)
+                if created:
+                    cert.save()
+                cert.users.add(user)
+
+            for dn in redundant_dns:
+                cert = X509Cert.objects.get(subject=dn)
+                cert.users.remove(user)
+
+    def backwards(self, orm):
+        LocalUser = orm['ligoauth.LocalUser']
+        X509Cert = orm['ligoauth.X509Cert']
+        for entry in users:
+            for dn in entry['dns']:
+                cert = X509Cert.objects.get(subject=dn)
+                cert.delete()
+
+            user = LocalUser.objects.get(username=entry['username'])
+            user.delete()                   
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'ligoauth.ligoldapuser': {
+            'Meta': {'object_name': 'LigoLdapUser', '_ormbases': ['auth.User']},
+            'ldap_dn': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'ligoauth.localuser': {
+            'Meta': {'object_name': 'LocalUser', '_ormbases': ['auth.User']},
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        'ligoauth.x509cert': {
+            'Meta': {'object_name': 'X509Cert'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'subject': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'})
+        }
+    }
+
+    complete_apps = ['ligoauth']
+    symmetrical = True
diff --git a/settings/branson.py b/settings/branson.py
index 476191ca6ba23f337d80a9e65c9023b28c70c94e..39c4d70bdfa81c0920276d49dc19289adc38818f 100644
--- a/settings/branson.py
+++ b/settings/branson.py
@@ -39,9 +39,6 @@ XMPP_ALERT_CHANNELS = [
                         'test_lowmass',
                       ]
  
-# SkyAlert
-SKYALERT_IVORN_PATTERN = "ivo://ligo.org/gracedb#%s-dev"
-
 # Latency histograms.  Where they go and max latency to bin.
 LATENCY_REPORT_DEST_DIR = "/home/branson/data/latency"
 LATENCY_REPORT_WEB_PAGE_FILE_PATH = LATENCY_REPORT_DEST_DIR + "/latency.inc"
diff --git a/settings/default.py b/settings/default.py
index e207015ac63f76389ad4dd812af4f60f6ba716d7..af32ed0dac389f5c718b3426242793d916e5ba25 100644
--- a/settings/default.py
+++ b/settings/default.py
@@ -62,9 +62,9 @@ DATABASES = {
 
 # SkyAlert
 
-SKYALERT_IVORN_PATTERN = "ivo://ligo.org/gracedb#%s"
+SKYALERT_IVORN_PATTERN = "ivo://gwnet/%s"
 SKYALERT_ROLE          = "test"
-SKYALERT_DESCRIPTION   = "LIGO / Virgo trigger"
+SKYALERT_DESCRIPTION   = "Report of a candidate gravitational wave event"
 SKYALERT_SUBMITTERS = ['Patrick Brady', 'Brian Moe']
 
 
diff --git a/templates/500.html b/templates/500.html
new file mode 100644
index 0000000000000000000000000000000000000000..b297a2c814b0ef4ee9af19c46387d8319f32a90d
--- /dev/null
+++ b/templates/500.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<!-- lifted from http://david.feinzeig.com/blog/2012/02/18/tips-for-creating-404-page-not-found-and-500-server-error-templates-in-django-plus-configuring-email-alerts/ -->
+ 
+<head>
+ <title>Page unavailable</title>
+</head>
+ 
+<body>
+ <h1>500: Page unavailable</h1>
+ 
+ <p>Sorry, but the requested page is unavailable due to a server hiccup.</p>
+ 
+ <p>The admins have been notified.</p>
+</body>
+ 
+</html>
+