
There is no dedicated Figment node in n8n. You connect to iMessage by calling Figment's REST API with n8n's standard HTTP Request node. The workflow pattern here adds two layers on top of that HTTP call: a human approval gate that holds every outgoing message until a reviewer confirms it, and an idempotency design that prevents duplicate sends when a step fails and is retried.
The workflow structure and node names are your design. The API endpoints and response shapes come from Figment.
ready. Check and start setup at /apple-connect/account. A signed-in Apple Account and a ready Messages service are distinct states; the API rejects send and read operations when the Messages service is not ready.messages:read (covers history and search) and messages:send (covers sends). Both scopes can be declared in one app registration and approved within a single grant, but they are issued separately. A conversation found in a read result is not automatically authorized for sends; the target conversation must be included in your messages:send scope when you register at /apple-connect/developers.Your connection_id (a con_... value) and a Figment access token are the two runtime credentials. Access tokens expire after 900 seconds — far shorter than a 24-hour approval wait. Because the approval gate suspends execution, the workflow must refresh the access token after approval and before the send request. The initial webhook payload must include the caller's current refreshToken. Your calling application must persist the rotated refresh_token returned by each refresh call; the previous value cannot be reused.
Create a Generic → Header Auth credential in n8n. Set the Name field to Authorization and the Value field to Bearer <your_access_token>. Use this credential for read operations, where your calling application supplies a current token at invocation time. The send step in the approval-gate workflow uses a dynamically refreshed token instead of this static credential.
| Step | Node | Purpose |
|---|---|---|
| 1 | Webhook Trigger | Receives intent — chatGuid, text, actionKey, connectionId, refreshToken — from your application |
| 2 | Code | Validates all required fields; throws on any missing value |
| 3 | Notification (your design) | Sends reviewer an approve/reject link embedding $execution.resumeUrl |
| 4 | Wait — On Webhook Call | Pauses until the reviewer responds or the timeout fires |
| 5 | IF | Routes on {"action":"approve"} in the resume callback body |
| 6 | HTTP Request | Refreshes the Figment access token using the stored refreshToken |
| 7 | HTTP Request | POSTs send with the fresh token and Idempotency-Key header |
| 8 | HTTP Request | GETs receipt using the same Idempotency-Key (requires messages:send) |
Webhook Trigger. n8n generates a production URL when the workflow is activated and a test URL while editing. Your application POSTs the message intent here. The chatGuid value must come from a prior call to the chats endpoint — not from a manually constructed phone string.
Wait node. Set the resume option to On Webhook Call. The node exposes $execution.resumeUrl, an execution-scoped URL to embed in the Step 3 notification alongside the proposed message text and chatGuid. Set a reasonable timeout; 24 hours suits most team-review scenarios. If the timeout fires without a callback, the execution ends without sending.
IF node. Check that the resume body contains {"action":"approve"}. Route the false branch to a Stop and Error node so rejected attempts appear in n8n's execution history. Only the true branch continues to the refresh and send steps.
HTTP Request — refresh token (Step 6).
https://figment.so/api/apple-connect/oauth/tokenapplication/x-www-form-urlencodedgrant_type=refresh_token, client_id=<your_client_id>, refresh_token referenced from the Code node's outputThe response includes a new access_token and a new refresh_token. Persist the new refresh_token in your application before proceeding; the previous value cannot be reused.
HTTP Request — send (Step 7). Set Authorization to Bearer <access_token> using an expression that references your Step 6 node's output by name — for example Bearer {{$('Refresh Token').first().json.access_token}}, where Refresh Token is your actual Step 6 node name. Reference connectionId, chatGuid, text, and actionKey from the Code node's output the same way. Do not use the static Header Auth credential here; it holds the token from before the approval wait.
https://figment.so/api/apple-connect/v1/connections/{connectionId}/messages/sendAuthorization: Bearer <refreshed_access_token>, Idempotency-Key: <actionKey>{"chatGuid":"<chatGuid>","text":"<text>"}The response envelope is {data:{idempotencyKey,state,guid?,delivery?,retrySend?},mode,replayed}.
HTTP Request — receipt (Step 8). Poll after a submitted or unknown state response. Use the same fresh token as Step 7.
https://figment.so/api/apple-connect/v1/connections/{connectionId}/messages/receiptAuthorization: Bearer <refreshed_access_token>, Idempotency-Key: <actionKey>Receipt states: submitted, delivered, read, failed, unknown, simulated (sandbox only). submitted confirms the bridge accepted the request — not that iMessage delivered it to the recipient. An unknown state means no recorded outcome; poll the receipt and do not resubmit under a new key.
The bridge binds each idempotency key to one fixed combination of app, connection, and request body. A second request with the same key and identical body returns the already-recorded result; a changed body is rejected.
The key must be generated by your calling application and persisted to a durable store before the first send request reaches the network. Do not derive the key from a hash of the message body — two separate decisions to send identical text to the same person would share a key, suppressing the second send. Do not regenerate the key on retry; use the original persisted value.
This Code node validates that the upstream contract was met (design example — not executed):
// actionKey must be generated and persisted by your application
// before it POSTs to this workflow's webhook URL.
const { chatGuid, text, actionKey, connectionId, refreshToken } = $input.first().json;
if (!actionKey || !refreshToken) {
throw new Error(
'actionKey and refreshToken are required. Generate and persist the actionKey; ' +
'supply the current refreshToken from your credential store.'
);
}
if (!chatGuid || !text || !connectionId) {
throw new Error('chatGuid, text, and connectionId are all required.');
}
return [{ json: { chatGuid, text, actionKey, connectionId, refreshToken } }];
If the send request at Step 7 times out before you receive a response, your persisted actionKey lets you poll the receipt endpoint safely — without risking a duplicate send under a new key.
The same Header Auth credential works for all read operations. Use n8n's query-parameter fields rather than hand-crafting query strings: a bare + in a URL query decodes as a space, corrupting phone-number-shaped chatGuid values.
| Operation | Method + path |
|---|---|
| List conversations | GET /v1/connections/{connection_id}/chats?limit=20&offset=0 |
| Messages in a chat | GET /v1/connections/{connection_id}/messages?chatGuid=<encoded>&limit=20 |
| Search messages | GET /v1/connections/{connection_id}/messages/search?query=<encoded>&limit=20 |
List and search responses use the envelope {data:[...],mode,offset,nextOffset,hasMore,historyComplete,scope}. Paginate using hasMore and nextOffset; check historyComplete before assuming a full corpus — history on a recently connected Mac may be incomplete. The API returns a guid on each chat item; use that value as chatGuid in subsequent requests.
Active live connections cost $29 per month per connection. Sandbox access is free and uses synthetic data; sandbox receipt states appear as simulated. Access starts after setup is complete.
Connect Apple Account · Manage connections and service status