textbee Logotextbee.dev
Save 44% with yearly billing.View Plans

E-commerce

WooCommerce SMS integration: order texts from your own phone with one code snippet

Published and updated

WooCommerce can text customers with about sixty lines of PHP and no SMS plugin. The snippet below hooks woocommerce_order_status_changed, builds a message per status from the order fields, and posts it to the textbee send endpoint with wp_remote_post. It lives in the Code Snippets plugin, so it survives theme updates and can be switched off in a click. A second snippet receives replies through a REST route and adds them to the order as a note.

Send
HTTP POST
to the textbee send endpoint
Receive
Webhook
MESSAGE_RECEIVED, flat JSON
Pace
10 to 15 / min
per Android phone
textbee code
None
uses what the tool ships today

What you need

  • A WordPress site with WooCommerce and the free Code Snippets plugin (Snippets in the admin menu). Any host that lets PHP make outbound HTTPS requests, which is nearly all of them.
  • A textbee account with the Android app installed on the phone that holds the SIM, and an API key from the dashboard.
  • Phone numbers the send endpoint can use. The snippet adds a default country code to numbers typed without one.
  • The textbee app on the phone. Download the app, then create an API key in the dashboard.

Send SMS from WooCommerce

WooCommerce fires a hook on every order status change. The snippet listens once, picks the template for the new status, and sends. Each status is texted once per order, recorded in order meta, so a status that flips back and forth does not text twice.

  1. Install Code Snippets

    In Plugins, add Code Snippets and activate it. A Snippets item appears in the admin menu. Snippets run from the database, so they stay in place when you switch or update the theme, and a snippet that throws a fatal error is switched off instead of taking the site down.

  2. Add the API key to wp-config.php

    Put the key in a constant above the line that says stop editing. It then stays out of the database and out of snippet exports. If you cannot edit wp-config.php, the snippet defines the same constant at the top.

    wp-config.php
    define( 'TEXTBEE_API_KEY', 'YOUR_TEXTBEE_API_KEY' );
  3. Create the send snippet

    Open Snippets, then Add New. Name it textbee order texts, paste the code below, keep the scope on Run snippet everywhere, and click Save Changes and Activate.

    textbee-order-texts.php
    // textbee order texts: one SMS per order status change.
    if ( ! defined( 'TEXTBEE_API_KEY' ) ) {
    	define( 'TEXTBEE_API_KEY', 'YOUR_TEXTBEE_API_KEY' );
    }
    define( 'TEXTBEE_DEFAULT_COUNTRY_CODE', '+1' ); // Added to numbers typed without one.
    
    add_action( 'woocommerce_order_status_changed', 'textbee_sms_on_status_change', 10, 4 );
    
    function textbee_sms_on_status_change( $order_id, $old_status, $new_status, $order ) {
    	$templates = array(
    		'processing' => 'Hi {name}, order #{order} is confirmed. Total {total}. We will text you when it ships.',
    		'completed'  => 'Hi {name}, order #{order} has shipped. Reply to this text if you have a question.',
    		'on-hold'    => 'Hi {name}, order #{order} is on hold until we receive your payment.',
    		'cancelled'  => 'Hi {name}, order #{order} was cancelled. Reply to this text if that is a mistake.',
    	);
    	if ( empty( $templates[ $new_status ] ) ) {
    		return;
    	}
    	$meta_key = '_textbee_sms_' . $new_status;
    	if ( $order->get_meta( $meta_key ) ) {
    		return; // Already texted for this status.
    	}
    	$phone = textbee_e164( $order->get_billing_phone() ?: $order->get_shipping_phone() );
    	if ( '' === $phone ) {
    		$order->add_order_note( 'textbee: no phone number on the order.' );
    		return;
    	}
    	$message = strtr( $templates[ $new_status ], array(
    		'{name}'  => $order->get_billing_first_name() ?: 'there',
    		'{order}' => $order->get_order_number(),
    		'{total}' => number_format( (float) $order->get_total(), 2 ) . ' ' . $order->get_currency(),
    	) );
    	$result = textbee_send_sms( $phone, $message );
    	$order->update_meta_data( $meta_key, $result );
    	$order->update_meta_data( '_textbee_phone', $phone ); // Lets the reply snippet find the order.
    	$order->save();
    	$order->add_order_note( 'textbee: ' . $result );
    }
    
    function textbee_send_sms( $phone, $message ) {
    	$response = wp_remote_post( 'https://api.textbee.dev/api/v1/gateway/send-sms', array(
    		'timeout' => 15,
    		'headers' => array(
    			'x-api-key'    => TEXTBEE_API_KEY,
    			'Content-Type' => 'application/json',
    		),
    		'body'    => wp_json_encode( array(
    			'recipients' => array( $phone ),
    			'message'    => $message,
    		) ),
    	) );
    	if ( is_wp_error( $response ) ) {
    		return 'error ' . $response->get_error_message();
    	}
    	$code = (int) wp_remote_retrieve_response_code( $response );
    	if ( $code < 200 || $code >= 300 ) {
    		return 'error ' . $code . ' ' . wp_remote_retrieve_body( $response );
    	}
    	$body = json_decode( wp_remote_retrieve_body( $response ), true );
    	return 'sent ' . ( $body['data']['smsBatchId'] ?? '' );
    }
    
    // Turns "(201) 555-0123" into "+12015550123". Numbers that start with + are kept as they are.
    function textbee_e164( $phone ) {
    	$phone  = trim( (string) $phone );
    	$digits = preg_replace( '/\D/', '', $phone );
    	if ( '' === $digits ) {
    		return '';
    	}
    	if ( str_starts_with( $phone, '+' ) ) {
    		return '+' . $digits;
    	}
    	if ( str_starts_with( $digits, '00' ) ) {
    		return '+' . substr( $digits, 2 );
    	}
    	return TEXTBEE_DEFAULT_COUNTRY_CODE . ltrim( $digits, '0' );
    }
  4. Adjust the templates and the country code

    Edit the $templates array: one line per status, with {name}, {order} and {total} filled from the order. Remove a status to skip it, or add the slug of a custom status a shipping plugin creates. Set TEXTBEE_DEFAULT_COUNTRY_CODE to the calling code of the country most customers type numbers for.

  5. Test with one order

    Create an order with your own number and move it to Processing, then Completed. Each change sends one text, and the order notes show textbee: sent with the batch id, or the error text. You never have to open a log file.

Receive SMS in WooCommerce

A second snippet registers a REST route that textbee posts replies to. It checks the signature, finds the newest order texted from that number, and adds the text as an order note, where the person handling the order sees it.

  1. Create the receive snippet

    Add a second snippet named textbee replies with the code below, scope Run snippet everywhere, and activate it. The signature check is the authentication, so the route needs no login.

    textbee-replies.php
    // textbee replies: adds each incoming SMS to the newest order texted from that number.
    define( 'TEXTBEE_WEBHOOK_SECRET', 'YOUR_SIGNING_SECRET' );
    
    add_action( 'rest_api_init', function () {
    	register_rest_route( 'textbee/v1', '/inbound', array(
    		'methods'             => 'POST',
    		'callback'            => 'textbee_receive_sms',
    		'permission_callback' => '__return_true', // The signature check below is the authentication.
    	) );
    } );
    
    function textbee_receive_sms( WP_REST_Request $request ) {
    	$raw       = $request->get_body();
    	$expected  = hash_hmac( 'sha256', $raw, TEXTBEE_WEBHOOK_SECRET );
    	$signature = (string) $request->get_header( 'x-signature' );
    	if ( ! hash_equals( $expected, $signature ) ) {
    		return new WP_REST_Response( array( 'error' => 'bad signature' ), 401 );
    	}
    	$payload = json_decode( $raw, true );
    	if ( ( $payload['webhookEvent'] ?? '' ) !== 'MESSAGE_RECEIVED' ) {
    		return new WP_REST_Response( array( 'ok' => true ), 200 );
    	}
    	$seen = 'textbee_' . md5( $payload['idempotencyKey'] ?? $raw );
    	if ( get_transient( $seen ) ) {
    		return new WP_REST_Response( array( 'ok' => true, 'duplicate' => true ), 200 );
    	}
    	set_transient( $seen, 1, DAY_IN_SECONDS );
    
    	$orders = wc_get_orders( array(
    		'meta_key'   => '_textbee_phone',
    		'meta_value' => $payload['sender'],
    		'limit'      => 1,
    		'orderby'    => 'date',
    		'order'      => 'DESC',
    	) );
    	if ( empty( $orders ) ) {
    		return new WP_REST_Response( array( 'ok' => true, 'matched' => false ), 200 );
    	}
    	$orders[0]->add_order_note( 'SMS reply from ' . $payload['sender'] . ': ' . $payload['message'] );
    	return new WP_REST_Response( array( 'ok' => true, 'matched' => true ), 200 );
    }
  2. Register the webhook in textbee

    In the textbee dashboard create a webhook with the URL https://yourstore.example/wp-json/textbee/v1/inbound, the MESSAGE_RECEIVED event and a signing secret. Put the same secret in the snippet.

  3. Test it

    Text the phone from the number on a test order. Within seconds the order gets a note with the message. A text from a number with no texted order answers matched false and stays in your textbee account.

  4. Answer from the store

    textbee_send_sms() from the first snippet can be called from any other snippet, for example a custom order action, with the number stored in _textbee_phone. The answer goes out from the same number the customer texted.

Ask your AI agent to set it up

The prompt below carries the endpoints, the payloads and the WooCommerce details from this page. Change the line that starts with What I want, paste the prompt into your agent, and it walks you through the setup with your own field names and numbers.

Prompt for your AI agent

Paste it into Claude, ChatGPT, Cursor or any coding agent. It carries the textbee API facts and the WooCommerce details the agent needs, so it does not guess.

I want to connect textbee to WooCommerce. textbee is an SMS gateway that sends and receives SMS through my own Android phone. Help me set it up step by step.

Facts about the textbee API. Use only these. Do not invent endpoints or fields.
- Send an SMS: POST https://api.textbee.dev/api/v1/gateway/send-sms with the header x-api-key: <TEXTBEE_API_KEY> and a JSON body like {"recipients": ["+12015550123"], "message": "Hello"}. recipients must be a JSON array of E.164 numbers, even for one number. Optional fields: deviceId, simSubscriptionId, scheduledAt (ISO 8601, in the future). The response is {"data": {"success": true, "smsBatchId": "..."}}.
- Check delivery or read replies: GET https://api.textbee.dev/api/v1/gateway/messages with the same header. Filters: direction=received, smsBatchId=<id from the send>, from and to as ISO timestamps. Each row has a status such as sent, delivered, failed or received.
- Receive an SMS: textbee posts JSON to a webhook URL I register in the dashboard. A MESSAGE_RECEIVED delivery has these top-level fields: smsId, message, deviceId, webhookSubscriptionId, webhookEvent, idempotencyKey, sender, receivedAt. The header X-Signature holds an HMAC-SHA256 hex digest of the raw body, keyed with the signing secret I set on the webhook. Retries reuse the same idempotencyKey.
- Limits: One Android phone sends roughly 10 to 15 messages a minute. Text only, no MMS. Messages over 160 characters are split into segments.

What I want: Text the customer from my own phone when a WooCommerce order is confirmed and when it is completed, and add their replies to the order as a note. The code must live in the Code Snippets plugin, not in the theme.

WooCommerce specifics you should know:
- WooCommerce fires woocommerce_order_status_changed with $order_id, $old_status, $new_status and the WC_Order object. Status slugs are pending, processing, on-hold, completed, cancelled, refunded and failed, without the wc- prefix.
- Read order fields with the getters: get_billing_phone(), get_shipping_phone(), get_billing_first_name(), get_order_number(), get_total(), get_currency(). Store state with update_meta_data() and save(), which work with and without high-performance order storage.
- Send with wp_remote_post() and wp_json_encode(); check is_wp_error() and wp_remote_retrieve_response_code(). Record the result with add_order_note() so it shows on the order.
- The code goes in the free Code Snippets plugin (Snippets, Add New, scope Run snippet everywhere), never in functions.php. Keep the API key in a wp-config.php constant.
- WooCommerce stores phone numbers as typed; normalise to E.164 with a default country code before sending.
- Replies come in through a REST route registered with register_rest_route on rest_api_init. Verify the X-Signature header with hash_hmac over the raw request body and hash_equals, and ignore repeated idempotencyKey values.

Give me:
1. The exact clicks and field values in WooCommerce, using its real field labels.
2. The request body or field mapping, with placeholders for my API key and phone numbers. Never ask me to paste the real key into this chat.
3. A test with one number and what a correct result looks like.
4. The three most likely failure causes and how to check each one.

Automations that work well

Confirmed and shipped
The processing and completed templates, which nearly every order passes through. Add the slug of a shipped status if your fulfilment plugin creates one, and drop completed to avoid two texts.
Cash on delivery confirmation
An on-hold template that asks for a YES reply, and three lines in the receive snippet that move the order to processing when the reply matches.
Low stock alert to the owner
A third snippet on woocommerce_low_stock that calls textbee_send_sms() with your own number and the product name.
New order to the owner
A second call to textbee_send_sms() in the processing branch with your number and a short line: New order #{order}, {total}. Many shops start with only this.

Why teams move WooCommerce SMS to their own phone

The bill
SMS plugins for WooCommerce charge a licence and pass on a per-message fee from a cloud provider, so the cost rises with every order. A phone on a flat plan sends the same texts for the plan price.
The reply rate
A shipping text from the shop’s own mobile number is one the customer can answer and call. Texts from a rented number get ignored, and replies to them go nowhere.
Code you can read
Sixty lines in a snippet, one endpoint, no plugin with access to every order and customer. When something changes, you edit a template line, not a settings maze.

A cloud SMS provider in WooCommerce vs textbee in WooCommerce

What you care aboutCloud SMS providertextbee
SetupInstall an SMS plugin, create the provider account it needs, buy a number, register the sender where required, map the statuses in the plugin settingsInstall Code Snippets, paste one snippet, add the key to wp-config.php: about ten minutes
Sender the contact seesA rented virtual number, or an alphanumeric sender where the country allows itYour own mobile number, which your contacts already have saved
Monthly costPer message, plus number rental and carrier surcharges in some countriesA flat plan with messages included: Pro is $14.99 a month for 5,000 messages. No per-message fee, no number rental, no carrier surcharge.
ThroughputSet by the provider tier and by sender registrationOne phone sends roughly 10 to 15 messages a minute. Add phones to scale.
RepliesInbound webhook from the provider, or the provider app in the tooltextbee webhook: flat JSON, signed with HMAC-SHA256
Where the message leaves fromThe provider data centerAn Android phone you own, plugged in, on Wi-Fi or mobile data
WordPress costPlugin licence plus the provider feesNone. Code Snippets is free

Per-message prices by provider and country, with sources and dates, are on the provider comparison pages.

What works and what does not

Works today

  • One text per status change, with the order fields in the message, and never twice for the same status
  • Custom order statuses added by other plugins: add the slug to the templates array
  • The result on the order as a note, so support sees whether the text went out without opening a log
  • Replies matched to the order by phone number, as an order note

Not with this route

  • Hosts that block outbound HTTP from PHP, which is rare; the order note shows the error
  • Tracking links, unless a shipment tracking plugin stores the URL in order meta the template can read
  • Marketing blasts to the customer list; that needs consent and a separate list
  • MMS

What WooCommerce charges

Code Snippets is free on WordPress.org, and PHP snippets that run on every request are part of the free version. WooCommerce itself is free. wordpress.org, checked on September 30, 2026. textbee itself charges a flat monthly plan with messages included; see pricing.

Frequently asked questions

Why a snippet instead of an SMS plugin?

An SMS plugin usually resells a cloud provider, so it adds a subscription and a per-message fee on top of the plugin, and it asks for access to every order. The snippet is sixty lines you can read, it calls one endpoint, and it sends through the phone on your desk. Turning it off is one click in Snippets.

Why not put the code in functions.php?

A theme update overwrites functions.php, and a syntax error there takes the whole site down. Code Snippets stores the code in the database, keeps it through theme changes, and switches off a snippet that throws a fatal error instead of the site.

Which status means shipped?

In stock WooCommerce, completed is the status a store sets when the order has been sent, so the completed template says shipped. If a shipping plugin adds its own status, add that slug to the templates array and remove completed so the customer gets one text.

Can I include a tracking link?

Yes, when a shipment tracking plugin stores the tracking URL in order meta. Read it with $order->get_meta() and the key that plugin documents, and add a {tracking} placeholder to the completed template. Use the carrier link as it is; shortened links get filtered more often.

Does this work with high-performance order storage?

Yes. The snippets use only the WC_Order getters, update_meta_data(), save() and wc_get_orders(), which work with both the legacy posts table and high-performance order storage.

What if the customer typed the phone without a country code?

textbee_e164() keeps a leading plus sign, turns a 00 prefix into a plus, and otherwise adds TEXTBEE_DEFAULT_COUNTRY_CODE and drops a leading zero. Set that constant to the calling code most of your customers use. A number the phone cannot reach comes back as MESSAGE_FAILED, and the order note holds the batch id to look it up.

Can I send the text only for some products or shipping methods?

Yes. Add a check at the top of textbee_sms_on_status_change(): loop over $order->get_items() for a product id, or compare $order->get_shipping_method() with the method name, and return early when it does not match.

Does the checkout slow down while the text is sent?

The send runs on the status change, which for card payments happens after the customer has paid, and wp_remote_post waits at most the timeout in the snippet. A busy store can queue the send with Action Scheduler so the status change returns at once.

How do I pause texts without deleting the snippet?

Deactivate it in Snippets. Orders keep changing status as usual and no texts go out. Activate it again and only new status changes send; nothing is sent late for the gap.

Can a reply change the order status?

Yes. In textbee_receive_sms(), after the order is found, check the message: when it is YES and the order is on hold, call update_status( "processing", "Confirmed by SMS" ) on it. Keep the match strict so a chatty reply does not move an order.

Will a plugin update remove the snippet?

No. The snippet is stored by Code Snippets in its own table and runs on every request, whatever WooCommerce, the theme or other plugins do. Export it from Snippets to keep a copy with your site backups.

How long does the setup take?

About five minutes with a phone at hand: install the textbee app, register the phone, create an API key, and paste the send snippet in Code Snippets. Receiving adds a webhook in the textbee dashboard and the receive snippet, which registers the REST route.

Sources

  1. wordpress.org, checked on September 30, 2026
  2. developer.woocommerce.com: Using the Code Snippets plugin, checked on September 30, 2026
  3. developer.wordpress.org: HTTP API, checked on September 30, 2026
  4. woocommerce.github.io: WC_Order reference, checked on September 30, 2026
  5. developer.wordpress.org: register_rest_route, checked on September 30, 2026

Read next

Your phone is already an SMS gateway. Switch it on.

Set up in minutes and send your first message for free. No credit card, no per-message fees, no carrier contracts.

Free tier forever No credit card required Open source

Need self-hosting help or a custom integration? See services