Loops
Loops is a marketing-automation and transactional-email platform for SaaS and e-commerce teams. Datafly Signal delivers your first-party events to the Loops Send Event API server-to-server, so a single behavioural event can identify a contact, update their properties, and trigger a Loops workflow — without loading Loops’ client-side code in the browser.
Prerequisites
- A Loops account with at least one event-triggered workflow or loop (or you can let Loops auto-create the event on first use).
- A Loops API key. Generate one in the Loops dashboard under Settings → API. Use a dedicated key for Signal so you can revoke it independently.
- Contacts are matched by email (or user ID), so your Signal events must carry an identified email or user ID. Anonymous-only events cannot be delivered to Loops.
Configuration
| Field | Required | Description |
|---|---|---|
| API Key | Yes | Your Loops team API key from Settings → API. Sent as an Authorization: Bearer header. Never expose this key client-side. |
Configure in Signal
- In the Signal management UI, open Integrations → Add integration and choose Loops.
- Paste your API Key.
- Select the Default preset. It maps the standard funnel — page views, sign-ups, logins, and the e-commerce conversion events — to Loops events, and routes contact fields and marketing consent to the contact record.
- Review the event mappings. Loops event names are free text and are created automatically on first use, so make sure the
eventNamevalues in your blueprint match the trigger names configured in your Loops loops/workflows. - Save and enable the pipeline.
API Endpoint
POST https://app.loops.so/api/v1/events/send
Authorization: Bearer <API_KEY>
Content-Type: application/jsonA successful request returns HTTP 200 with { "success": true }. Validation failures return 400 with { "success": false, "message": "..." }. The API is rate-limited to 10 requests per second per team; Signal automatically throttles delivery and retries 429 responses with exponential backoff.
Identity Signals
Loops resolves each event to a contact using plaintext identifiers — there is no hashed-identity surface:
| Signal field | Loops field | Notes |
|---|---|---|
email (trait) | email | Primary contact identifier. Required unless userId is present. |
user_id | userId | Alternative identifier. Either email or userId must be present. |
The contact is created automatically if it does not already exist. Standard contact properties — firstName, lastName, and userGroup — are updated from the same event when present.
Consent
Marketing consent maps to the Loops subscribed contact flag. When context.consent.canonical.marketing is true, Signal sets contactProperties.subscribed = true. When consent is absent or denied, Signal omits the field so an existing contact is never silently unsubscribed by a missing signal — manage suppression in Loops itself.
Event Mapping
Signal sends GA4-style snake_case event names. Event-specific data is nested under eventProperties (used for email personalisation), and contact fields are nested under contactProperties (saved onto the contact).
| Signal event | Loops eventName |
|---|---|
page | page_view |
Signed Up | signed_up |
Logged In | logged_in |
Product Viewed | product_viewed |
Product Added | product_added_to_cart |
Checkout Started | checkout_started |
Order Completed | order_completed |
Example
A Datafly.js call:
datafly.track("Order Completed", {
order_id: "ORD-1042",
revenue: 89.97,
currency: "GBP",
tax: 14.99,
shipping: 4.99,
coupon: "WELCOME10",
});
datafly.identify("user_8821", {
email: "sam@example.com",
first_name: "Sam",
});produces this request to Loops:
{
"email": "sam@example.com",
"userId": "user_8821",
"eventName": "order_completed",
"contactProperties": {
"firstName": "Sam"
},
"eventProperties": {
"order_id": "ORD-1042",
"revenue": 89.97,
"currency": "GBP",
"tax": 14.99,
"shipping": 4.99,
"coupon": "WELCOME10"
}
}The order_completed event triggers any Loops workflow listening for it, and eventProperties such as revenue and order_id are available as merge fields in the workflow’s emails.
Testing
- Trigger an identified event (for example, complete a test purchase) on your site.
- In Signal, open the Event Debugger and confirm the event delivered to Loops with HTTP
200and{ "success": true }. - In the Loops dashboard, open the contact (by email) and confirm the event appears in their activity timeline and that any mapped contact properties were updated.
- If you wired the event to a workflow, confirm the workflow entered and the email rendered the expected
eventPropertiesmerge fields.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
400 { "success": false } | Missing email and userId, or missing eventName. | Ensure the event carries an identified email or user ID and that the blueprint sets eventName. |
401 / 403 | Invalid or revoked API key. | Regenerate the key in Settings → API and update the integration. |
409 Conflict | An idempotency key was reused within 24 hours. | Expected for genuine duplicates; Signal treats it as a permanent (non-retried) outcome. |
429 Too Many Requests | Exceeded 10 requests/second per team. | Signal retries automatically with backoff; if sustained, reduce event volume or contact Loops about your limit. |
| Workflow doesn’t trigger | eventName doesn’t match the trigger configured in Loops. | Align the blueprint’s eventName with the exact name used in your Loops workflow. |
| Properties missing in email | Data sent as a contact property instead of an event property (or vice versa). | Event-template merge fields read from eventProperties; contact fields read from contactProperties. Check the mapping target prefix. |