Subscribe to a Webhook response triggered by a specific device

Hello folks,

What is the best practice to subscribe to a webhook response that was trigger by a specific device?

Here is the scenario:

I have Photon A and Photon B, both calling the same get_weather webhook.
Each Photon provides its location to the webhook in order to get the local temperature.

What is the best way to have Photon A only react to webhook responses that where trigger by itself (same for Photon B)?

One way would be to encode the Photon ID in the webhook response, have each Photon subscribe and parse all the webhook responses, and do some action only when the ID is their own. This solution seems inefficient though. Is there a better way?

Thanks

1 Like

Baaed on the documentation available at http://docs.particle.io/core/webhooks/, you may have to provide one webhook for each of the location from where you want to get the location. As given in the documentation, you may have to register two webhooks.

$ particle webhook GET get_weather1 http://w1.weather.gov/xml/current_obs/LOC1.xml
$ particle webhook GET get_weather2 http://w1.weather.gov/xml/current_obs/LOC2.xml

where LOC1 and LOC2 are the weather station codes

Accordingly, the photon A and B have to publish and subscribe to different webhooks.

This is based on my understanding of the documentation. , I would be certainly eager to know better approaches.

:

2 Likes

See: http://docs.particle.io/core/webhooks/#webhook-options-deviceid

1 Like

@TheVelozGroup, thanks for the idea. It would definitely work, but it would become very hard to manage once you have many products. Imagine, if you have 1,000 product out there, you would need 1,000 Webhooks. All of them being exactly the same.

@kennethlimcp, the deviceID function described in the documentation (if Iā€™m understanding it correctly) could enable the Webhook to respond only to PhotonA and it would ignore request from PhotonB.

But what I want to do is for the same Webhook to respond to PhotonA when PhotonA publishes the event, and to PhotonB when PhotonB publishes the event. @Dave, is there a way to do this?

1 Like

Hi All,

Great discussion! Hmmā€¦

So lets say we want to offer a data service via webhooks, providing the weather to any device:

webhook:

{
    "event": "get_weather",
    "url": "http://api.openweathermap.org/data/2.5/weather?q={{loc}}&mode=json",
    "requestType": "GET",
    "responseTemplate": "{{#weather}}{{description}}{{/weather}}",
    "mydevices": false
}

So now anyone publishing ā€œget_weatherā€, with a JSON contents with ā€˜locā€™ set will trigger this hook:

Spark.publish("get_weather", "{ \"loc\": \"Shenzhen\"}");

So, now we have a webhook that anyone or any of your products can share, but we donā€™t want every single device to hear every single weather response for everyone else, we only want to hear messages relevant to usā€¦

So far, the easiest way to do that is change the client subscription based on the published event name, but that doesnā€™t work well here. Soā€¦ Hmmmmmā€¦

We already have the concept of a response templateā€¦ what if we had a ā€œresponseTopicā€ setting, so you could key in the same way? In this case, we might add this to the hook:

#proposed setting:
responseTopic: "hook/weather/{{loc}}"

So then weā€™d subscribe to:

Spark.subscribe("hook/weather/Shenzhen", gotWeatherData);

Would that work? :slight_smile:

Thanks!
David

1 Like

David the responseTopic setting would do the trick perfectly!

Having said that, I would use it in a slightly different way. Instead of setting it by location, I would set it by deviceID.

Webook:

responseTopic: "hook/weather/{{SPARK_CORE_ID}}

Firmware:

Spark.publish("get_weather", "{ \"loc\": \"Shenzhen\"}");
...
String myID = Spark.deviceID();
Spark.subscribe(String("hook/weather/" + myID), gotWeatherData);

Imagine you have 600 devices running in Shenzhen, each one of them program to ping the webhook every 10 minutes and then update the LCD. If we set the responseTopic by location, in the worst case scenario, each device in Shenzen would be updating the LCD every 1 second (10min / 600) instead of every 10 min.

Love the feature! Canā€™t wait to responseTopic!!

1 Like

So I have been thinking and messing around and read this from @ScruffR

made me think... "what about using Particle.subscribe() and using the device's own ID" like this:

void setup()
{
  myDeviceID = System.deviceID();
  Particle.variable("deviceID", myDeviceID, STRING);
  Particle.variable("SunriseTime", &message, STRING);
  Particle.subscribe("hook-response/sun_time", sunTimeHandler, myDeviceID);
}

but, it doesn't seem to respond...

entire test code:

struct deviceTime{
  int Hour;
  int Minute;
};

deviceTime sunrise = {6,0};
deviceTime sunset = {18,30};

String myDeviceID;

char message[40] = "No Time Values Recieved";

void setup()
{
  myDeviceID = System.deviceID();
  Particle.variable("deviceID", myDeviceID, STRING);
  Particle.variable("SunriseTime", &message, STRING);
  Particle.subscribe("hook-response/sun_time", sunTimeHandler, myDeviceID);
}

void loop()
{
  static unsigned long myTimer = 0;
  if(millis() - myTimer > 60000UL)
  {
    Spark.publish("sun_time", "{ \"myCity\": \"Weston\", \"myState\": \"FL\" }");
    myTimer = millis();
  }
}

void sunTimeHandler(const char * event, const char * data)
{
  String sunriseReturn = String(data);
  char sunriseBuffer[40] = "";
  sunriseReturn.toCharArray(sunriseBuffer, 40);
  sunrise.Hour = atoi(strtok(sunriseBuffer, "\"~"));
  sunrise.Minute = atoi(strtok(NULL, "~"));
  sunset.Hour = atoi(strtok(NULL, "~"));
  sunset.Minute = atoi(strtok(NULL, "~"));
  char buffer[60] = "";
  sprintf(buffer, "Sunrise: %02d:%02d, Sunset: %02d:%02d", sunrise.Hour, sunrise.Minute, sunset.Hour, sunset.Minute);
  Particle.publish("pushover", String(buffer), 60, PRIVATE);
  strcpy (message, buffer);
}

though the hook does fire and of course the myDeviceID is correct in the response (redacted):

{"name":"sun_time","data":"{ "myCity": "Weston", "myState": "FL" }","ttl":"60","published_at":"2015-08-29T13:21:21.314Z","coreid":"myCoreID"}
{"name":"hook-response/sun_time/0","data":""7~00~19~44~"","ttl":"60","published_at":"2015-08-29T13:21:24.500Z","coreid":"undefined"}

but you can see that I get a:"coreid":"undefined" argument returned in the response...

Am I missing something here?

webhook:

{
"event": "sun_time",
"url": "http://api.wunderground.com/api/myApiKey/astronomy/q/{{myState}}/{{myCity}}.json",
"requestType": "POST",
"headers": null,
"query": null,
"responseTemplate": "{{#sun_phase}}{{sunrise.hour}}~{{sunrise.minute}}~{{sunset.hour}}~{{sunset.minute}}~{{/sun_phase}}",
"json": null,
"auth": null,
"coreid": "{{SPARK_CORE_ID}}",
"mydevices": true
}

This is a bit of a late response, but I did modify webhooks to allow setting the deviceid to "{{SPARK_CORE_ID}}", so I think this should be supported now.

Thanks,
David

Hey @Dave, Iā€™ve read through the documentation but I am still unclear as to what I need to do in order to get the response when I pass deviceID.

What Iā€™m trying to do is:

  • Make a webhook call with a specific device
  • My server performs an action and sends a response
  • Only the device that sent the request handles the response

Hereā€™s my webhook:

{
    "event": "init",
    "url": "https://foo.com/api/devices/particle/{{SPARK_CORE_ID}}/id",
    "requestType": "GET",
    "mydevices": true,
    "deviceid": "{{SPARK_CORE_ID}}", // Added this because I read something about it in the Docs...???
    "coreid": "{{SPARK_CORE_ID}}"
}

And here is my code:

Particle.subscribe("hook-response/init", &FooClass::Initialized, this, System.deviceID());
Particle.publish("init", (const char *)0, 10, PRIVATE);

If I replace System.deviceID() with MY_DEVICES then the callback worksā€¦ I have read through the docs and forum posts but I canā€™t find what Iā€™m missing.

Hi @jlkalberer, I love the responseTopic feature. Here is how it goes:

On your webhook:

{
	"event": "get_location",
	"url": "https://neutrinoapi.com/ip-info",
	"requestType": "POST",
	"query": {
		"ip": "{{myIP}}",
		"mode": "json"
	},
	"responseTopic": "hook-response/get_location_{{SPARK_CORE_ID}}",
	"json": null,
	"auth": null,
	"mydevices": true
}

On your firmware:

Particle.subscribe(String("hook-response/get_location_" + System.deviceID()), locationHandler, MY_DEVICES); 

Particle.publish("get_location", "{\"myIP\":\"" + myIP + "\"}", 60, PRIVATE);

You can deploy this firmware to all your Photons. Only when the hook is responding to that specific Photonā€™s call, that deviceā€™s locationHandler will be call.

Hope this helps.

1 Like

Ahhh, ok. Thatā€™s easy enoughā€¦ Thanks.