VOEvent Notices via GCN Classic

In this section, we’ll write a GCN handler function, write a script to receive and parse VOEvent notices using PyGCN, and finally download and parse an example notice. You do not need to create an account or set up credentials to receive GCN classic notices.

Receiving and Parsing Notices

The following basic handler function will parse out the URL of the FITS file, download it, and extract the probability sky map. We decorate the handler with @gcn.handlers.include_notice_types to specify that we only want to process certain GCN notice types (LVC_PRELIMINARY, LVC_INITIAL, and LVC_UDPATE).


Note that mock or ‘test’ observations are denoted by the role="test" VOEvent attribute. Alerts resulting from real LIGO/Virgo/KAGRA science data will always have role="observation". The sample code below will respond only to ‘test’ events. When preparing for actual observations, you must remember to switch to ‘observation’ events.


Observe in the example below that we do not have to explicitly download the FITS file because the hp.read_map() function works with either URLs or filenames. However, you could download and save the FITS file in order to save it locally using astropy.utils.data.download_file(), requests.get(), urllib.request.urlopen(), or even curl.

import gcn
import healpy as hp
import ligo.skymap
import ligo.skymap.io

# Function to call every time a GCN is received.
# Run only for notices of type
def process_gcn(payload, root):
    # Respond only to 'test' events.
    # VERY IMPORTANT! Replace with the following code
    # to respond to only real 'observation' events.
    # if root.attrib['role'] != 'observation':
    #    return
    if root.attrib['role'] != 'test':

    # Read all of the VOEvent parameters from the "What" section.
    params = {elem.attrib['name']:
              for elem in root.iterfind('.//Param')}

    if params['AlertType'] == 'Retraction':
        print(params['GraceID'], 'was retracted')

    # Respond only to 'CBC' events. Change 'CBC' to 'Burst'
    # to respond to only unmodeled burst events.
    if params['Group'] != 'CBC':

    # Print all parameters.
    for key, value in params.items():
        print(key, '=', value)

    if 'skymap_fits' in params:
        # Read the HEALPix sky map and the FITS header.
        skymap, header = ligo.skymap.io.read_sky_map(params['skymap_fits'])
        header = dict(header)

        # Print some values from the FITS header.
        print('Distance =', header['distmean'], '+/-', header['diststd'])

Listen for GCNs

Now, we will start the VOEvent client to listen for GCNs using the gcn.listen function. By default, this will connect to the anonymous, public GCN server. You just need to tell gcn.listen what function to call whenever it receives an GCN; in this example, that is the process_gcn handler that we defined above.

# Listen for GCNs until the program is interrupted
# (killed or interrupted with control-C).

When you run this script you should receive a sample LIGO/Virgo/KAGRA GCN Notice every hour. For each event received it will print output that looks like what is shown in the Offline Testing example below.


gcn.listen will try to automatically reconnect if the network connection is ever broken.

Offline Testing

Sometimes it is convenient to be able to explicitly call the GCN handler with a sample input, rather than waiting for the next broadcast of a sample alert. You can download the example GCN notices from this documentation and pass it into your GCN handler at any time. First, download the sample GCN notice:

$ curl -O https://emfollow.docs.ligo.org/userguide/_static/MS181101ab-2-Preliminary.xml

Then you can manually invoke your GCN handler using this Python code:

import lxml.etree
payload = open('MS181101ab-2-Preliminary.xml', 'rb').read()
root = lxml.etree.fromstring(payload)
process_gcn(payload, root)

Upon running this, you should see:

Packet_Type = 150
internal = 0
Pkt_Ser_Num = 2
GraceID = MS181101ab
AlertType = Preliminary
HardwareInj = 0
OpenAlert = 1
EventPage = https://example.org/superevents/MS181101ab/view/
Instruments = H1,L1,V1
FAR = 9.11069936486e-14
Group = CBC
Pipeline = gstlal
Search = MDC
skymap_fits = https://emfollow.docs.ligo.org/userguide/_static/bayestar.multiorder.fits,0
BNS = 0.95
NSBH = 0.01
BBH = 0.03
Terrestrial = 0.01
HasNS = 0.95
HasRemnant = 0.91
HasMassGap = 0.01
Distance = 39.76999609489013 +/- 8.308435058808886