diff --git a/gstlal-inspiral/python/inspiral.py b/gstlal-inspiral/python/inspiral.py
index 90863117ddbfd063e4c1eeb2cca92d419d00a3e4..76c56249722d8f025fcf078e3a5055607e70526f 100644
--- a/gstlal-inspiral/python/inspiral.py
+++ b/gstlal-inspiral/python/inspiral.py
@@ -76,6 +76,7 @@ from ligo.lw.utils import time_slide as ligolw_time_slide
 import lal
 from lal import LIGOTimeGPS
 from lal import series as lalseries
+from lalburst.snglcoinc import light_travel_time
 import ligo.gracedb.rest
 from ligo import gracedb
 
@@ -553,8 +554,6 @@ class GracedBWrapper(object):
 			# part way through.
 			#
 
-			if self.verbose:
-				print >>sys.stderr, "sending %s to gracedb ..." % filename
 			print >>sys.stderr, "sending %s to gracedb ..." % filename
 			message = StringIO.StringIO()
 			xmldoc = last_coincs[coinc_event.coinc_event_id]
@@ -562,22 +561,124 @@ class GracedBWrapper(object):
 			# columns (attributes should all be
 			# populated).  FIXME:  ugly.
 			sngl_inspiral_table = lsctables.SnglInspiralTable.get_table(xmldoc)
+			process_params_table = lsctables.ProcessParamsTable.get_table(xmldoc)
 			for standard_column in ("process:process_id", "ifo", "search", "channel", "end_time", "end_time_ns", "end_time_gmst", "impulse_time", "impulse_time_ns", "template_duration", "event_duration", "amplitude", "eff_distance", "coa_phase", "mass1", "mass2", "mchirp", "mtotal", "eta", "kappa", "chi", "tau0", "tau2", "tau3", "tau4", "tau5", "ttotal", "psi0", "psi3", "alpha", "alpha1", "alpha2", "alpha3", "alpha4", "alpha5", "alpha6", "beta", "f_final", "snr", "chisq", "chisq_dof", "bank_chisq", "bank_chisq_dof", "cont_chisq", "cont_chisq_dof", "sigmasq", "rsqveto_duration", "Gamma0", "Gamma1", "Gamma2", "Gamma3", "Gamma4", "Gamma5", "Gamma6", "Gamma7", "Gamma8", "Gamma9", "spin1x", "spin1y", "spin1z", "spin2x", "spin2y", "spin2z", "event_id"):
 				try:
 					sngl_inspiral_table.appendColumn(standard_column)
 				except ValueError:
 					# already has it
 					pass
+			# If we have snr time series for a detector that didn't
+			# produce a peak above threshold, create a trigger here
+			# for the highest peak that is coincident with all
+			# other triggers
+			event_ifos = [event.ifo for event in last_coincs.sngl_inspirals(coinc_event.coinc_event_id)]
+			triggerless_ifos = [ifo for ifo in self.instruments if ifo not in event_ifos]
+			subthreshold_events = []
+			# FIXME Add logic to take highest network snr set of triggers when more than 1 sub-threshold trigger
+			for ifo in triggerless_ifos:
+				trigger_time_list = sorted([(event.ifo, LIGOTimeGPS(event.end_time, event.end_time_ns), getattr(event, "%s_snr_time_series" % ifo)) for event in last_coincs.sngl_inspirals(coinc_event.coinc_event_id) if getattr(event, "%s_snr_time_series" % ifo) is not None], key = lambda t: t[1])
+				if not trigger_time_list:
+					continue
+				# NOTE NOTE NOTE The coincidence finding algorithm has an extra fudge factor added to the light travel time that isn't used here
+				coinc_segment = ligolw_segments.segments.segment(ligolw_segments.segments.NegInfinity, ligolw_segments.segments.PosInfinity)
+				t0 = trigger_time_list[0][2].epoch
+				# NOTE This assumes all ifos have same sample rate
+				dt = trigger_time_list[0][2].deltaT
+				unit = trigger_time_list[0][2].sampleUnits
+				for (trigger_ifo, trigger_time, snr_time_series) in trigger_time_list:
+					coincidence_window = LIGOTimeGPS(light_travel_time(ifo, trigger_ifo))
+					coinc_segment &= ligolw_segments.segments.segment(trigger_time - coincidence_window, trigger_time + coincidence_window)
+					if snr_time_series.epoch == t0:
+						snr_time_series_array = snr_time_series.data.data
+					else:
+						idx = int(round((t0 + (len(snr_time_series_array))*dt - snr_time_series.epoch) / dt))
+						snr_time_series_array = numpy.append(snr_time_series_array, snr_time_series.data.data[idx:])
+
+				if not snr_time_series_array.any():
+					# empty snr time series, detector wasn't on
+					# FIXME Need to fix itacac so that snr time series only created from a detector if there was science mode data, currently it'll just pass zeros
+					continue
+
+				for (event, snr_time_series) in subthreshold_events:
+					coincidence_window = LIGOTimeGPS(light_travel_time(ifo, event.ifo))
+					trigger_time = LIGOTimeGPS(event.end_time, event.end_time_ns)
+					coinc_segment &= ligolw_segments.segments.segment(trigger_time - coincidence_window, trigger_time + coincidence_window)
+
+				tfinal = t0 + dt*(snr_time_series_array.shape[0] - 1)
+				# NOTE This will not work if the length of the
+				# snr time series (currently the
+				# autocorrelation length) is not large enough
+				# to cover all possible times that could be
+				# coincident with all of the existing triggers
+				if not ((t0 <= coinc_segment[0]) and (tfinal >= coinc_segment[1])):
+					# NOTE This should probably be an
+					# assert, but it's better to upload a
+					# candidate to gracedb without
+					# subthreshold triggers than to not
+					# upload anything
+					print >>sys.stderr, "something went wrong creating sub-threshold trigger for %s in gracedb upload" % ifo
+					continue
+				idx0 = int((coinc_segment[0] - t0)/dt)
+				idxf = int(math.ceil((coinc_segment[1] - t0)/dt))
+				peak_snr = 0.
+				for idx in xrange(idx0, idxf + 1):
+					if abs(snr_time_series_array[idx]) > peak_snr:
+						peak_snr = abs(snr_time_series_array[idx])
+						peak_phase = math.atan2(snr_time_series_array[idx].imag, snr_time_series_array[idx].real)
+						peak_idx = idx
+						peak_t = idx*dt + t0
+
+				# NOTE Bayestar needs at least 26.3ms on either side of the snr peak, so only provide an snr time series if we have enough samples
+				min_num_samps = int(math.ceil(0.0263 / dt)) + 1
+				if peak_idx < min_num_samps or snr_time_series_array.shape[0] - peak_idx < min_num_samps:
+					print >>sys.stderr, "not enough samples to produce snr time series for sub-threshold trigger in %s" % ifo
+					continue
+
+				sngl_inspiral_table.append(sngl_inspiral_table.RowType())
+				# FIXME Ugly
+				for column in sngl_inspiral_table.columnnames:
+					setattr(sngl_inspiral_table[-1], column, getattr(sngl_inspiral_table[0], column))
+				# TODO Make sure end_time_gmst is not set
+				setattr(sngl_inspiral_table[-1], "ifo", ifo)
+				setattr(sngl_inspiral_table[-1], "end_time", peak_t.gpsSeconds)
+				setattr(sngl_inspiral_table[-1], "end_time_ns", peak_t.gpsNanoSeconds)
+				setattr(sngl_inspiral_table[-1], "end_time_gmst", lal.GreenwichMeanSiderealTime(peak_t))
+				setattr(sngl_inspiral_table[-1], "snr", peak_snr)
+				setattr(sngl_inspiral_table[-1], "coa_phase", peak_phase)
+				setattr(sngl_inspiral_table[-1], "chisq", numpy.nan)
+				setattr(sngl_inspiral_table[-1], "event_id", sngl_inspiral_table.get_next_id())
+				setattr(sngl_inspiral_table[-1], "eff_distance", numpy.nan)
+				for row in process_params_table:
+					if row.param == "--state-channel-name" and row.value[:2] == ifo:
+						setattr(sngl_inspiral_table[-1], "channel", row.value[3:])
+						break
+
+				snr_time_series = lal.CreateCOMPLEX8TimeSeries(
+					name = "snr",
+					epoch = peak_t - min_num_samps*dt,
+					f0 = 0.0,
+					deltaT = dt,
+					sampleUnits = unit,
+					length = 2*min_num_samps + 1
+				)
+				snr_time_series.data.data = snr_time_series_array[(peak_idx - min_num_samps):(peak_idx + min_num_samps+1)]
+				subthreshold_events.append((sngl_inspiral_table[-1], snr_time_series))
+
 			# add SNR time series if available
 			# FIXME Probably only want one time series for each ifo
 			for event in last_coincs.sngl_inspirals(coinc_event.coinc_event_id):
-				for ifo in ("H1", "L1", "V1", "K1"):
-					snr_time_series = getattr(event, "%s_snr_time_series" % ifo)
-					if snr_time_series is not None:
-						snr_time_series_element = lalseries.build_COMPLEX8TimeSeries(snr_time_series)
-						snr_time_series_element.appendChild(ligolw_param.Param.from_pyvalue(u"event_id", event.event_id))
-						snr_time_series_element.appendChild(ligolw_param.Param.from_pyvalue(u"ifo", ifo))
-						xmldoc.childNodes[-1].appendChild(snr_time_series_element)
+				snr_time_series = getattr(event, "%s_snr_time_series" % event.ifo)
+				if snr_time_series is not None:
+					snr_time_series_element = lalseries.build_COMPLEX8TimeSeries(snr_time_series)
+					snr_time_series_element.appendChild(ligolw_param.Param.from_pyvalue(u"event_id", event.event_id))
+					xmldoc.childNodes[-1].appendChild(snr_time_series_element)
+
+			for (event, snr_time_series) in subthreshold_events:
+				snr_time_series_element = lalseries.build_COMPLEX8TimeSeries(snr_time_series)
+				snr_time_series_element.appendChild(ligolw_param.Param.from_pyvalue(u"event_id", event.event_id))
+				xmldoc.childNodes[-1].appendChild(snr_time_series_element)
+
 			# translate IDs from integers to ilwd:char for
 			# backwards compatibility
 			ilwdify.do_it_to(xmldoc)
diff --git a/gstlal-inspiral/python/snglinspiraltable.c b/gstlal-inspiral/python/snglinspiraltable.c
index ee714e827215555b9da70c33d87fae1fe6a388e2..53aa340d00060b06e06610a9441525d6d61108b1 100644
--- a/gstlal-inspiral/python/snglinspiraltable.c
+++ b/gstlal-inspiral/python/snglinspiraltable.c
@@ -623,7 +623,7 @@ static PyObject *from_buffer(PyObject *cls, PyObject *args)
 				PyErr_SetString(PyExc_ValueError, "buffer overrun while copying H1 SNR time series");
 				return NULL;
 			}
-			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("H1_snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->H1_length);
+			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->H1_length);
 			if (!series)
 			{
 				Py_DECREF(item);
@@ -650,7 +650,7 @@ static PyObject *from_buffer(PyObject *cls, PyObject *args)
 				PyErr_SetString(PyExc_ValueError, "buffer overrun while copying L1 SNR time series");
 				return NULL;
 			}
-			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("L1_snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->L1_length);
+			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->L1_length);
 			if (!series)
 			{
 				Py_DECREF(item);
@@ -677,7 +677,7 @@ static PyObject *from_buffer(PyObject *cls, PyObject *args)
 				return NULL;
 			}
 
-			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("V1_snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->V1_length);
+			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->V1_length);
 			if (!series)
 			{
 				Py_DECREF(item);
@@ -702,7 +702,7 @@ static PyObject *from_buffer(PyObject *cls, PyObject *args)
 				PyErr_SetString(PyExc_ValueError, "buffer overrun while copying SNR time series");
 				return NULL;
 			}
-			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("K1_snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->K1_length);
+			COMPLEX8TimeSeries *series = XLALCreateCOMPLEX8TimeSeries("snr", &gstlal_snglinspiral->epoch, 0., gstlal_snglinspiral->deltaT, &lalDimensionlessUnit, gstlal_snglinspiral->K1_length);
 			if (!series)
 			{
 				Py_DECREF(item);