I think you need to mark the node as dirty -
p.setDirty() - and also
c.setChanged(). I'm not sure why the first doesn't also set the second, but it doesn't.
You might also want to save undo data. Here's an example where I write text into the body and make it undoable. The outline asks if I want to save before closing.
p, u, = c.p, c.undoer
bunch = u.beforeChangeBody(p)
title = p.h
title += '\n' + '-'*(len(title) + 1)
time_string = ':created: ' + c.getTime(body=True) + '\n'
zettel_str = f'{title}\n\n:id: {p.gnx}\n{time_string}\n'
w = c.frame.body.wrapper
w.setInsertPoint(0)
p.b = zettel_str + p.b
u.afterChangeBody(p, 'zettel-convert-card', bunch)
For undoing a headline change, you could use u.beforeChangeHeadline() / u.afterChangeHeadline() instead. I haven't tried this to change a headline but I presume the node and outline would also show as changed.