Typically a heartbeat means that there is a bit (or pair of bits) that changes states on a regular basis because: One controller always clears the bit when it is set (or makes the two bits equal), and the other controller always tries to set the bit when it is clear (or make the two bits opposite), so the bit changes states as fast as the two devices are programmed to communicate. If either controller sees the bit stay in the same state too long, comms have stopped, so there are at least a couple of timers involved as well.
Another method is to simply use a pair of counters. The counter gets incremented and sent along with the rest of the data, the CPU on the other end updates a value on its end and sends it back (it can increment its own value or simply make it equal, depending on whether or not you need to "make up" lost messages if one ever gets skipped).
Using incrementing values has the advantage of providing a great visual cue for how fast and how repeatable the comms are occurring. When this value stays the same (EQU to a stored copy) for a period of time, then comms have stopped.
If you logic does either of the above, and either controller can tell when the other one is offline then it is complete. Actually, as long as it meets your requirement in this application then it is complete. My description may give you some food for thought though.