Two-Way Sync
Sync edits made in Google Sheets back to your WordPress site in real time. Setup, security model, echo suppression, and the sheetlinkwp_sheet_update developer hook.
What it does
SheetLink's default direction is one-way: a form submission lands in your Google Sheet. Two-Way Sync adds the reverse path - when you (or a teammate) edits a cell in your Google Sheet, the change arrives back at your WordPress site within seconds, where you can react to it with a custom hook.
Common uses: marking a lead as contacted in the Sheet and having the plugin update the corresponding WP user/post, scoring a row from cold to hot and triggering a follow-up email, syncing inventory edits from a Sheet back to a WooCommerce product. SheetLink writes the change into your WP database using a developer-friendly action hook; your code decides what to do with each edit.
Two-Way Sync is a paid add-on. Free tier sites have form submissions flow one direction (WordPress -> Google Sheets) only; the inbound REST endpoint is registered but rejects requests without an active Two-Way Sync entitlement.
How it works
Two-Way Sync uses a three-stage relay:
- Apps Script onEdit trigger in your Google Sheet. Every cell edit fires a function that POSTs the row data to SheetLink's hosted relay at
api.sheetlinkwp.com. - SheetLink relay validates the license token, looks up the activation, and forwards the payload to your WordPress site's REST endpoint at
/wp-json/sheetlinkwp/v1/two-way/incoming. - Your WordPress site verifies the license-derived shared secret in the
X-SheetLinkWP-Secretrequest header, applies echo suppression (so the plugin's own writes do not loop back), logs the event, and fires thesheetlinkwp_sheet_updateaction.
The relay sits in the middle so your site does not need a publicly-resolvable URL pre-known to the Sheet, and the Apps Script does not need to embed your WordPress credentials. The relay forwards based on the license token alone; it never sees the WordPress site's secret.
Prerequisites
- SheetLink Forms plugin installed and activated
- An active SheetLink license with the Two-Way Sync add-on (Agency Plus tier includes it; available a-la-carte at $29/mo)
- A Google Sheet that already receives form submissions from SheetLink
- Permission to install Apps Script triggers on that Sheet
Setup
- In your WordPress admin go to SheetLink -> Two-Way Sync. The page shows a generated Apps Script tailored to your license token.
- Open the Google Sheet that receives your form submissions. Click Extensions -> Apps Script.
- Replace the contents of the
Code.gsfile with the script copied from the Two-Way Sync admin page. - Save the script. Click the triggers icon in the left sidebar (the small clock).
- Click Add Trigger and configure: function =
onEdit, event source = From spreadsheet, event type = On edit. Authorize when prompted. - Edit any cell on a non-header row. Within a few seconds the edit appears in SheetLink -> Two-Way Sync -> Recent Events.
If no event appears: check the SheetLink delivery log for entries from the relay, and confirm your license is showing the Two-Way Sync entitlement on the SheetLink -> License page.
Security model
Three independent layers gate the inbound endpoint:
- License entitlement check. Before doing anything else, the handler returns 403 unless
SheetLink_License::can('two_way_sync')is true. Free-tier sites cannot receive Two-Way Sync events even if the relay attempts to deliver one. - Shared-secret header. The plugin derives a per-site secret from the active license token and expects it in the
X-SheetLinkWP-Secretrequest header. Verification useshash_equals()to defeat timing attacks. Mismatched or missing secret -> 403. - Payload license token. The Apps Script POSTs the license token in the JSON body so the relay can route the request to the correct site without the script needing to know the WordPress URL.
The Apps Script never holds your WordPress site's secret. The secret only ever exists in two places: your WP database (derived from license activation) and the SheetLink relay's lookup table (set when the license activates). Rotating the license token rotates the secret automatically.
Echo suppression - avoiding edit loops
If your Two-Way Sync code reacts to an inbound edit by writing back to the same Sheet (e.g. enriching a row when it changes), Apps Script's onEdit would fire again and bounce the same payload back. Two-Way Sync includes built-in echo suppression to break the loop:
- Every outbound write the plugin makes is recorded in a small ring buffer for 60 seconds.
- When an inbound payload arrives, the handler checks whether its
databundle matches anything in the recent-write buffer. - On match, the handler short-circuits with
{received: true, suppressed: true, reason: 'echo'}. No log entry, no action fire, no listener round-trip.
The relay still receives a 200 so it does not retry. The match is structural (key + value comparison), not byte-exact, so equivalent payloads with reordered fields are still recognised.
Reacting to edits - the sheetlinkwp_sheet_update hook
For developers: the plugin fires a sheetlinkwp_sheet_update action every time a (non-suppressed) edit arrives. Hook into it to apply your business logic.
add_action( 'sheetlinkwp_sheet_update', function ( $sheet_id, $row, $data ) {
// $sheet_id - the Google Sheet ID (spreadsheet)
// $row - 1-indexed row number that was edited
// $data - column-name => value map for the entire row
// Example: when a lead is marked 'closed-won' in the Sheet, fire a
// confirmation email and stamp the WP user with the deal value.
if ( ( $data['Status'] ?? '' ) === 'closed-won' ) {
$email = sanitize_email( $data['Email'] ?? '' );
$value = (int) preg_replace( '/[^0-9]/', '', $data['Deal Value'] ?? '0' );
do_my_post_close_workflow( $email, $value );
}
}, 10, 3 );The action runs synchronously inside the REST request, so heavy work belongs in wp_schedule_single_event() or a queued job rather than directly in the listener. The endpoint must respond within ~25 seconds or the relay treats it as a failure.
Troubleshooting
The Recent Events panel never updates. Open the Google Apps Script editor, switch to the Executions tab, and look for failed onEdit runs. The most common cause is an unauthorised trigger - re-add the trigger and accept the OAuth prompt. The second most common is an Apps Script-side restriction on outbound HTTP; this is rare on consumer Google accounts but happens on tightly-locked Google Workspace orgs.
The endpoint returns 403 Invalid secret. Re-activate your license on the SheetLink -> License page. The shared secret is derived from the license token; if you regenerated the license or pasted in the wrong key, the Apps Script-supplied token will not derive the same secret. Re-activation rotates everything in lockstep.
Edits seem to be ignored intermittently. This is usually echo suppression catching legitimate same-data edits within the 60-second window (e.g. someone sets a field to the same value twice in quick succession, both as part of the same workflow). Add a timestamp column to your Sheet so each edit has a unique signature, and the suppression matcher will treat them as distinct.
The Two-Way Sync admin menu does not appear. The page is gated on the two_way_sync license entitlement. Activate a license on a tier that includes it (Agency Plus or the a-la-carte Two-Way Sync add-on) and the menu appears after the next page reload.
Ready to Get Started?
Install SheetLink Forms and connect your first form in under 10 minutes.